From 1eab6145a919c6cad89f5140811ca28408cc7482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 3 Oct 2026 23:07:16 +0200 Subject: [PATCH] fix(symptoms): show the load error in the quick entry instead of the empty hint --- .../__tests__/symptom-entry-form.test.tsx | 78 ++++++++---- .../symptoms/symptom-entry-form.tsx | 118 ++++++++++-------- 2 files changed, 119 insertions(+), 77 deletions(-) diff --git a/src/components/symptoms/__tests__/symptom-entry-form.test.tsx b/src/components/symptoms/__tests__/symptom-entry-form.test.tsx index 1fa6cf5fe..a5f67d51f 100644 --- a/src/components/symptoms/__tests__/symptom-entry-form.test.tsx +++ b/src/components/symptoms/__tests__/symptom-entry-form.test.tsx @@ -7,13 +7,16 @@ * - drop the `resolvedAt === null` filter on the episodes → "offers no * episode selector when every episode is resolved" goes red; * - render the add chip unconditionally → "a delegate who may log but not - * manage gets no add chip" goes red. + * manage gets no add chip" goes red; + * - drop the `definitions.isError` branch → "a failed read shows the error + * row, not the empty hint" goes red (the empty hint renders instead). */ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; const state = vi.hoisted(() => ({ canManage: true, + definitionsFailed: false, episodes: [] as Array<{ id: string; label: string; @@ -34,30 +37,42 @@ vi.mock("@/components/illness/use-illness", () => ({ })); vi.mock("../use-symptoms", () => ({ - useSymptomDefinitions: () => ({ - isLoading: false, - data: { - limit: 8, - definitions: [ - { - id: "d1", - label: "Aura", - icon: "Zap", - sortOrder: 0, - isActive: true, - recent: { count30d: 0, maxIntensity30d: null, lastOccurredAt: null }, + useSymptomDefinitions: () => + state.definitionsFailed + ? { isLoading: false, isError: true, data: undefined, refetch: vi.fn() } + : { + isLoading: false, + isError: false, + data: { + limit: 8, + definitions: [ + { + id: "d1", + label: "Aura", + icon: "Zap", + sortOrder: 0, + isActive: true, + recent: { + count30d: 0, + maxIntensity30d: null, + lastOccurredAt: null, + }, + }, + { + id: "d2", + label: "Headache", + icon: "Brain", + sortOrder: 1, + isActive: true, + recent: { + count30d: 0, + maxIntensity30d: null, + lastOccurredAt: null, + }, + }, + ], + }, }, - { - id: "d2", - label: "Headache", - icon: "Brain", - sortOrder: 1, - isActive: true, - recent: { count30d: 0, maxIntensity30d: null, lastOccurredAt: null }, - }, - ], - }, - }), useLogSymptomEvent: () => ({ mutateAsync: vi.fn(), isPending: false }), useCreateSymptomDefinition: () => ({ mutateAsync: vi.fn(), @@ -111,4 +126,19 @@ describe("", () => { ]; expect(render()).not.toContain("-episode"); }); + + it("a failed read shows the error row, not the empty hint", () => { + state.canManage = true; + state.episodes = []; + state.definitionsFailed = true; + try { + const html = render(); + expect(html).toContain('data-slot="query-error-row"'); + expect(html).toContain("load your symptoms"); + expect(html).not.toContain("first symptom"); + expect(html).not.toContain('data-testid="symptom-add-chip"'); + } finally { + state.definitionsFailed = false; + } + }); }); diff --git a/src/components/symptoms/symptom-entry-form.tsx b/src/components/symptoms/symptom-entry-form.tsx index 7198e8825..cca703874 100644 --- a/src/components/symptoms/symptom-entry-form.tsx +++ b/src/components/symptoms/symptom-entry-form.tsx @@ -27,6 +27,7 @@ import { import { FieldGroup } from "@/components/ui/field-group"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { QueryErrorRow } from "@/components/ui/query-error-row"; import { Select, SelectContent, @@ -210,59 +211,70 @@ export function SymptomEntryForm({ >
-
- {list.map((definition) => { - const selected = definitionId === definition.id; - return ( - - ); - })} - {canDefine && !atLimit ? ( - { - setDefinitionId(id); - setSymptomError(null); - }} - /> - ) : null} -
- {list.length === 0 && !definitions.isLoading ? ( -

- {canDefine - ? t("symptoms.entry.emptyHint") - : t("symptoms.entry.emptyHintNoManage")} -

- ) : atLimit && canDefine ? ( -

- {t("symptoms.entry.limitHint", { limit })} -

- ) : null} + {definitions.isError ? ( + // A failed read is not an empty list: the empty hint would invite + // the person to define a symptom they may already have. + void definitions.refetch()} + /> + ) : ( + <> +
+ {list.map((definition) => { + const selected = definitionId === definition.id; + return ( + + ); + })} + {canDefine && !atLimit ? ( + { + setDefinitionId(id); + setSymptomError(null); + }} + /> + ) : null} +
+ {list.length === 0 && !definitions.isLoading ? ( +

+ {canDefine + ? t("symptoms.entry.emptyHint") + : t("symptoms.entry.emptyHintNoManage")} +

+ ) : atLimit && canDefine ? ( +

+ {t("symptoms.entry.limitHint", { limit })} +

+ ) : null} + + )} {symptomError ? (