mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(symptoms): size the icon pickers for touch and name each icon in the reader's language
This commit is contained in:
1 parent
1eab6145a9
commit
367a1bb4b4
10 files changed
+279
-3
No files matched your search
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "z. B. Aura",
|
||||
"iconLabel": "Symbol",
|
||||
"icons": {
|
||||
"Tag": "Etikett",
|
||||
"Activity": "Aktivität",
|
||||
"Heart": "Herz",
|
||||
"HeartPulse": "Herzschlag",
|
||||
"Brain": "Gehirn",
|
||||
"Zap": "Blitz",
|
||||
"Flame": "Flamme",
|
||||
"Snowflake": "Schneeflocke",
|
||||
"Droplet": "Tropfen",
|
||||
"CircleDot": "Punkt",
|
||||
"BatteryLow": "Leerer Akku",
|
||||
"MoonStar": "Mond und Stern",
|
||||
"PersonStanding": "Person",
|
||||
"Drama": "Masken",
|
||||
"Frown": "Trauriges Gesicht",
|
||||
"Cookie": "Keks",
|
||||
"Soup": "Suppe",
|
||||
"Pill": "Tablette",
|
||||
"Thermometer": "Thermometer",
|
||||
"Stethoscope": "Stethoskop"
|
||||
},
|
||||
"limitReached": "Du hast 8 Symptome. Blende eins aus, um ein neues anzulegen.",
|
||||
"saveError": "Das Symptom konnte nicht gespeichert werden. Bitte versuch es noch einmal.",
|
||||
"nameRequired": "Gib dem Symptom einen Namen.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g. Aura",
|
||||
"iconLabel": "Icon",
|
||||
"icons": {
|
||||
"Tag": "Tag",
|
||||
"Activity": "Activity",
|
||||
"Heart": "Heart",
|
||||
"HeartPulse": "Heartbeat",
|
||||
"Brain": "Brain",
|
||||
"Zap": "Lightning",
|
||||
"Flame": "Flame",
|
||||
"Snowflake": "Snowflake",
|
||||
"Droplet": "Drop",
|
||||
"CircleDot": "Dot",
|
||||
"BatteryLow": "Low battery",
|
||||
"MoonStar": "Moon and star",
|
||||
"PersonStanding": "Person",
|
||||
"Drama": "Masks",
|
||||
"Frown": "Frowning face",
|
||||
"Cookie": "Cookie",
|
||||
"Soup": "Soup",
|
||||
"Pill": "Pill",
|
||||
"Thermometer": "Thermometer",
|
||||
"Stethoscope": "Stethoscope"
|
||||
},
|
||||
"limitReached": "You have 8 symptoms. Hide one to add another.",
|
||||
"saveError": "Couldn't save the symptom. Please try again.",
|
||||
"nameRequired": "Give the symptom a name.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Nombre",
|
||||
"namePlaceholder": "p. ej. Aura",
|
||||
"iconLabel": "Icono",
|
||||
"icons": {
|
||||
"Tag": "Etiqueta",
|
||||
"Activity": "Actividad",
|
||||
"Heart": "Corazón",
|
||||
"HeartPulse": "Latido",
|
||||
"Brain": "Cerebro",
|
||||
"Zap": "Rayo",
|
||||
"Flame": "Llama",
|
||||
"Snowflake": "Copo de nieve",
|
||||
"Droplet": "Gota",
|
||||
"CircleDot": "Punto",
|
||||
"BatteryLow": "Batería baja",
|
||||
"MoonStar": "Luna y estrella",
|
||||
"PersonStanding": "Persona",
|
||||
"Drama": "Máscaras",
|
||||
"Frown": "Cara triste",
|
||||
"Cookie": "Galleta",
|
||||
"Soup": "Sopa",
|
||||
"Pill": "Pastilla",
|
||||
"Thermometer": "Termómetro",
|
||||
"Stethoscope": "Estetoscopio"
|
||||
},
|
||||
"limitReached": "Tienes 8 síntomas. Oculta uno para añadir otro.",
|
||||
"saveError": "No se pudo guardar el síntoma. Inténtalo de nuevo.",
|
||||
"nameRequired": "Ponle un nombre al síntoma.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Nom",
|
||||
"namePlaceholder": "p. ex. Aura",
|
||||
"iconLabel": "Icône",
|
||||
"icons": {
|
||||
"Tag": "Étiquette",
|
||||
"Activity": "Activité",
|
||||
"Heart": "Cœur",
|
||||
"HeartPulse": "Battement de cœur",
|
||||
"Brain": "Cerveau",
|
||||
"Zap": "Éclair",
|
||||
"Flame": "Flamme",
|
||||
"Snowflake": "Flocon de neige",
|
||||
"Droplet": "Goutte",
|
||||
"CircleDot": "Point",
|
||||
"BatteryLow": "Batterie faible",
|
||||
"MoonStar": "Lune et étoile",
|
||||
"PersonStanding": "Personne",
|
||||
"Drama": "Masques",
|
||||
"Frown": "Visage triste",
|
||||
"Cookie": "Biscuit",
|
||||
"Soup": "Soupe",
|
||||
"Pill": "Comprimé",
|
||||
"Thermometer": "Thermomètre",
|
||||
"Stethoscope": "Stéthoscope"
|
||||
},
|
||||
"limitReached": "Tu as 8 symptômes. Masques-en un pour en ajouter un autre.",
|
||||
"saveError": "Impossible d'enregistrer le symptôme. Réessaie.",
|
||||
"nameRequired": "Donne un nom au symptôme.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Nome",
|
||||
"namePlaceholder": "es. Aura",
|
||||
"iconLabel": "Icona",
|
||||
"icons": {
|
||||
"Tag": "Etichetta",
|
||||
"Activity": "Attività",
|
||||
"Heart": "Cuore",
|
||||
"HeartPulse": "Battito",
|
||||
"Brain": "Cervello",
|
||||
"Zap": "Fulmine",
|
||||
"Flame": "Fiamma",
|
||||
"Snowflake": "Fiocco di neve",
|
||||
"Droplet": "Goccia",
|
||||
"CircleDot": "Punto",
|
||||
"BatteryLow": "Batteria scarica",
|
||||
"MoonStar": "Luna e stella",
|
||||
"PersonStanding": "Persona",
|
||||
"Drama": "Maschere",
|
||||
"Frown": "Viso triste",
|
||||
"Cookie": "Biscotto",
|
||||
"Soup": "Zuppa",
|
||||
"Pill": "Pillola",
|
||||
"Thermometer": "Termometro",
|
||||
"Stethoscope": "Stetoscopio"
|
||||
},
|
||||
"limitReached": "Hai 8 sintomi. Nascondine uno per aggiungerne un altro.",
|
||||
"saveError": "Impossibile salvare il sintomo. Riprova.",
|
||||
"nameRequired": "Dai un nome al sintomo.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "이름",
|
||||
"namePlaceholder": "예: 전조",
|
||||
"iconLabel": "아이콘",
|
||||
"icons": {
|
||||
"Tag": "태그",
|
||||
"Activity": "활동",
|
||||
"Heart": "하트",
|
||||
"HeartPulse": "심장 박동",
|
||||
"Brain": "뇌",
|
||||
"Zap": "번개",
|
||||
"Flame": "불꽃",
|
||||
"Snowflake": "눈송이",
|
||||
"Droplet": "물방울",
|
||||
"CircleDot": "점",
|
||||
"BatteryLow": "배터리 부족",
|
||||
"MoonStar": "달과 별",
|
||||
"PersonStanding": "사람",
|
||||
"Drama": "가면",
|
||||
"Frown": "찡그린 얼굴",
|
||||
"Cookie": "쿠키",
|
||||
"Soup": "수프",
|
||||
"Pill": "알약",
|
||||
"Thermometer": "체온계",
|
||||
"Stethoscope": "청진기"
|
||||
},
|
||||
"limitReached": "증상이 8개입니다. 하나를 숨기면 새로 추가할 수 있습니다.",
|
||||
"saveError": "증상을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"nameRequired": "증상 이름을 입력하세요.",
|
||||
|
||||
@@ -11529,6 +11529,28 @@
|
||||
"nameLabel": "Nazwa",
|
||||
"namePlaceholder": "np. Aura",
|
||||
"iconLabel": "Ikona",
|
||||
"icons": {
|
||||
"Tag": "Etykieta",
|
||||
"Activity": "Aktywność",
|
||||
"Heart": "Serce",
|
||||
"HeartPulse": "Bicie serca",
|
||||
"Brain": "Mózg",
|
||||
"Zap": "Błyskawica",
|
||||
"Flame": "Płomień",
|
||||
"Snowflake": "Płatek śniegu",
|
||||
"Droplet": "Kropla",
|
||||
"CircleDot": "Kropka",
|
||||
"BatteryLow": "Słaba bateria",
|
||||
"MoonStar": "Księżyc i gwiazda",
|
||||
"PersonStanding": "Osoba",
|
||||
"Drama": "Maski",
|
||||
"Frown": "Smutna twarz",
|
||||
"Cookie": "Ciastko",
|
||||
"Soup": "Zupa",
|
||||
"Pill": "Tabletka",
|
||||
"Thermometer": "Termometr",
|
||||
"Stethoscope": "Stetoskop"
|
||||
},
|
||||
"limitReached": "Masz 8 objawów. Ukryj jeden, aby dodać kolejny.",
|
||||
"saveError": "Nie udało się zapisać objawu. Spróbuj ponownie.",
|
||||
"nameRequired": "Nadaj objawowi nazwę.",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Both symptom icon pickers (the add chip's popover and the edit sheet) share
|
||||
* one shape: 44 px targets on a phone, 36 px from `sm`, and a translated
|
||||
* accessible name per icon rather than the raw Lucide component name.
|
||||
*
|
||||
* The popover and the sheet portal, and portals do not render server-side,
|
||||
* so both are replaced by pass-through wrappers that render their children
|
||||
* inline; what is measured is the picker inside them.
|
||||
*
|
||||
* Mutation checks (each run, each seen red):
|
||||
* - put `size-7` back on the add chip's icon buttons → "the add chip's
|
||||
* icon targets" goes red;
|
||||
* - put `aria-label={name}` back on either picker → the label assertions
|
||||
* go red ("HeartPulse" is a raw name, "Heartbeat" its English label).
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { ReactNode } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
vi.mock("@/components/ui/popover", () => ({
|
||||
Popover: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
PopoverTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
PopoverContent: ({ children }: { children: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/ui/responsive-sheet", () => ({
|
||||
ResponsiveSheet: ({
|
||||
children,
|
||||
footer,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
}) => (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("../use-symptoms", () => {
|
||||
const mutation = {
|
||||
mutateAsync: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
isPending: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
};
|
||||
return {
|
||||
useCreateSymptomDefinition: () => mutation,
|
||||
useUpdateSymptomDefinition: () => mutation,
|
||||
useUpdateSymptomEvent: () => mutation,
|
||||
};
|
||||
});
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { SYMPTOM_ICON_ALLOWLIST } from "@/lib/symptoms/shared";
|
||||
import { AddSymptomChip } from "../add-symptom-chip";
|
||||
import { EditSymptomDefinitionSheet } from "../symptom-edit-sheets";
|
||||
|
||||
function iconButtons(html: string): string[] {
|
||||
return html.match(/<button[^>]*role="radio"[^>]*>/g) ?? [];
|
||||
}
|
||||
|
||||
function assertPicker(html: string) {
|
||||
const buttons = iconButtons(html);
|
||||
expect(buttons).toHaveLength(SYMPTOM_ICON_ALLOWLIST.length);
|
||||
for (const button of buttons) {
|
||||
expect(button).toContain("size-11");
|
||||
expect(button).toContain("sm:size-9");
|
||||
expect(button).not.toMatch(/\bsize-7\b/);
|
||||
}
|
||||
expect(html).toContain('aria-label="Heartbeat"');
|
||||
expect(html).toContain('aria-label="Low battery"');
|
||||
expect(html).not.toContain('aria-label="HeartPulse"');
|
||||
expect(html).not.toContain('aria-label="BatteryLow"');
|
||||
}
|
||||
|
||||
describe("symptom icon pickers", () => {
|
||||
it("the add chip's icon targets are 44 px on a phone, with translated names", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<AddSymptomChip onCreated={() => {}} />
|
||||
</I18nProvider>,
|
||||
);
|
||||
assertPicker(html);
|
||||
});
|
||||
|
||||
it("the edit sheet's picker matches, with translated names", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<EditSymptomDefinitionSheet
|
||||
definition={{
|
||||
id: "d1",
|
||||
label: "Aura",
|
||||
icon: "Zap",
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
recent: {
|
||||
count30d: 0,
|
||||
maxIntensity30d: null,
|
||||
lastOccurredAt: null,
|
||||
},
|
||||
}}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
assertPicker(html);
|
||||
});
|
||||
|
||||
it("every allowlisted icon has a label in the English bundle", async () => {
|
||||
const en = (await import("../../../../messages/en.json")).default as {
|
||||
symptoms: { icons: Record<string, string> };
|
||||
};
|
||||
for (const name of SYMPTOM_ICON_ALLOWLIST) {
|
||||
expect(en.symptoms.icons[name], name).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -115,10 +115,10 @@ export function AddSymptomChip({
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={selected}
|
||||
aria-label={name}
|
||||
aria-label={t(`symptoms.icons.${name}`)}
|
||||
onClick={() => setIcon(name)}
|
||||
className={cn(
|
||||
"focus-visible:ring-ring/50 grid size-7 place-items-center rounded-md border transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
||||
"focus-visible:ring-ring/50 grid size-11 place-items-center rounded-md border transition-colors focus-visible:ring-2 focus-visible:outline-none sm:size-9",
|
||||
selected
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
: "border-border text-muted-foreground hover:bg-accent",
|
||||
|
||||
@@ -241,7 +241,7 @@ export function EditSymptomDefinitionSheet({
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={selected}
|
||||
aria-label={name}
|
||||
aria-label={t(`symptoms.icons.${name}`)}
|
||||
onClick={() => setIcon(name)}
|
||||
className={cn(
|
||||
"focus-visible:ring-ring/50 grid size-11 place-items-center rounded-md border transition-colors focus-visible:ring-2 focus-visible:outline-none sm:size-9",
|
||||
|
||||
Reference in new issue
Block a user