fix(dashboard): show a read failure on the Vorsorge tile, not an empty state

The tile already read the query's error flag but never branched on it, so
a failed reminder read fell through to "no upcoming checkup reminders" —
a missed preventive-care nudge hiding behind an honest-looking empty
state. Add a QueryErrorRow with retry before the empty check. Test
watched fail first against the fall-through.
This commit is contained in:
Marc-André Bombeck committed 2026-08-09 04:08:57 +02:00
1 parent 48e4882400
commit 51c40c61d9
2 files changed
+63 -1

No files matched your search

@@ -0,0 +1,57 @@
/**
* A reminder-read failure on the dashboard Vorsorge tile must not read as "no
* upcoming checkup reminders" — a missed preventive-care nudge is the harm
* behind that fall-through. `useMeasurementReminders` is forced into its error
* state; the tile has to render a `query-error-row` with retry and never the
* empty copy.
*
* SSR-static render; the query-backed hooks are mocked so the error branch is
* synchronous.
*/
import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
vi.mock("@/hooks/use-measurement-reminders", () => ({
useMeasurementReminders: () => ({
data: undefined,
isError: true,
refetch: vi.fn(),
}),
useMeasurementReminderMutations: () => ({
satisfy: { mutate: vi.fn(), isPending: false },
}),
}));
vi.mock("@/hooks/use-encounters", () => ({
useEncounters: () => ({ data: { upcoming: [] } }),
}));
vi.mock("@/hooks/use-record-capabilities", () => ({
useRecordCapabilities: () => ({ canManage: true, canAdd: true }),
}));
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
usePathname: () => "/",
useSearchParams: () => new URLSearchParams(),
}));
import { I18nProvider } from "@/lib/i18n/context";
import { VorsorgeDashboardCard } from "../vorsorge-dashboard-card";
function render() {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">
<VorsorgeDashboardCard />
</I18nProvider>,
);
}
describe("<VorsorgeDashboardCard> — a read failure is honest, not empty", () => {
it("renders the query-error row with retry and never the empty copy", () => {
const html = render();
expect(html).toContain('data-slot="query-error-row"');
expect(html).toContain('data-slot="query-error-row-retry"');
expect(html).not.toContain("No upcoming checkup reminders");
});
});
@@ -32,6 +32,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ListRow } from "@/components/ui/list-row";
import { QueryErrorRow } from "@/components/ui/query-error-row";
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
import { Skeleton } from "@/components/ui/skeleton";
import { MeasurementForm } from "@/components/measurements/measurement-form";
@@ -78,7 +79,7 @@ export function VorsorgeDashboardCard() {
// the dashboard summary offering what the dedicated page withholds was the
// tell that this row had never been asked.
const { canManage } = useRecordCapabilities();
const { data: reminders, isError } = useMeasurementReminders();
const { data: reminders, isError, refetch } = useMeasurementReminders();
// A query's `isLoading` is not a hydration-safe branch: TanStack reports the
// optimistic mount fetch on the client's very first render and cannot on the
@@ -153,6 +154,10 @@ export function VorsorgeDashboardCard() {
<Skeleton key={i} className="h-12 w-full" />
))}
</div>
) : isError ? (
// A read failure must not read as "no upcoming reminders" — a missed
// checkup is the harm behind that fall-through (§6).
<QueryErrorRow onRetry={() => refetch()} />
) : upcoming.length === 0 ? (
<div className="space-y-2">
<p className="text-muted-foreground text-sm">