Files
HealthLog/src/components/settings/__tests__/medications-section.test.tsx
T
Marc-André Bombeck f4815fafe8 refactor(settings): drop the unused section heading frame
SettingsSectionFrame re-rendered the page heading and subtitle that
SettingsShell already paints from the section slug. No route used it;
only the per-section smoke tests rendered bodies through it to assert the
visible heading and its id.

Remove the component and move the heading wrapper those tests rely on
into a test-only harness that reproduces the shell's heading markup, so
the smoke tests keep asserting what the user actually sees.
2026-06-27 12:46:23 +02:00

152 lines
5.7 KiB
TypeScript

/**
* v1.16.10 — the "Medikamente" settings section (`/settings/medications`).
*
* Hosts the /medications list customisation following the dashboard /
* insights pattern: the cards ⇄ table view preference (the shared header
* toggle, optimistic PUT) plus the manual-order editor INLINE (two
* grouped sections, explicit Save). SSR smoke assertions, matching the
* rest of the settings suite — the editor's interactive contract is
* pinned in `medication-order-editor.test.tsx`, the PUT side in
* `use-medication-list-layout.test.ts`.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import type { MedicationListLayout } from "@/lib/medication-list-layout";
const MEDS = [
{ id: "a1", name: "Ramipril", dose: "5 mg", active: true },
{ id: "a2", name: "Aspirin", dose: "100 mg", active: true },
{ id: "i1", name: "Amoxicillin", dose: "500 mg", active: false },
];
// Mutable holder so individual tests can inject a layout into the mocked
// `useQuery` without re-mocking the module.
const queryState: { layout: MedicationListLayout } = {
layout: { version: 1, view: "cards", order: [] },
};
vi.mock("@tanstack/react-query", () => ({
useQuery: ({ queryKey }: { queryKey: readonly unknown[] }) => {
if (Array.isArray(queryKey) && queryKey[0] === "medication-list-layout") {
return { data: queryState.layout, isLoading: false };
}
if (Array.isArray(queryKey) && queryKey[0] === "medications") {
return { data: MEDS, isLoading: false };
}
return { data: null, isLoading: false };
},
useQueryClient: () => ({
setQueryData: vi.fn(),
getQueryData: vi.fn(),
invalidateQueries: vi.fn(),
}),
// v1.18.0 (S5) — the gathered injection-sites card uses `useMutation`.
useMutation: () => ({ mutate: vi.fn(), isPending: false }),
}));
vi.mock("sonner", () => ({
toast: { success: vi.fn(), error: vi.fn() },
}));
vi.mock("@/lib/charts/reduced-motion", () => ({
prefersReducedMotion: () => true,
}));
// v1.18.0 (S5) — the section now reads `useAuth()` to gate the
// injection-sites card it gathered from the account profile.
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({
user: { id: "u1", role: "USER" },
isAuthenticated: true,
isLoading: false,
refetch: vi.fn(),
}),
}));
import { I18nProvider } from "@/lib/i18n/context";
import { SettingsSectionFrame } from "./section-frame-harness";
import { MedicationsSection } from "../medications-section";
function render(locale: "en" | "de" = "en"): string {
// v1.18.6 (W9) — the visible heading (with the historic id) comes from the
// shared frame the route wraps the section in.
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>
<SettingsSectionFrame slug="medications">
<MedicationsSection />
</SettingsSectionFrame>
</I18nProvider>,
);
}
beforeEach(() => {
queryState.layout = { version: 1, view: "cards", order: [] };
});
describe("<MedicationsSection> — SSR smoke", () => {
it("renders the section heading via i18n (no raw keys)", () => {
// v1.18.1 (D0) — the section blurb was dropped; the sr-only heading + the
// card content carry the meaning.
const html = render();
expect(html).toContain("settings-section-medications-title");
expect(html).not.toContain("settings.sections.");
expect(html).not.toContain("medications.view");
expect(html).not.toContain("medications.reorder");
});
it("renders the German copy end-to-end", () => {
const html = render("de");
expect(html).toContain("Reihenfolge anpassen");
expect(html).toContain(
"Wähle, ob deine Medikamente als Karten oder als Tabelle angezeigt werden.",
);
});
it("hosts the view toggle bound to the persisted layout (cards pressed)", () => {
const html = render();
expect(html).toMatch(
/data-slot="medications-view-cards"[^>]*aria-pressed="true"|aria-pressed="true"[^>]*data-slot="medications-view-cards"/,
);
expect(html).toMatch(
/data-slot="medications-view-table"[^>]*aria-pressed="false"|aria-pressed="false"[^>]*data-slot="medications-view-table"/,
);
});
it("reflects a persisted table view on the toggle", () => {
queryState.layout = { version: 1, view: "table", order: [] };
const html = render();
expect(html).toMatch(
/data-slot="medications-view-table"[^>]*aria-pressed="true"|aria-pressed="true"[^>]*data-slot="medications-view-table"/,
);
});
it("renders the order editor INLINE with active rows before inactive", () => {
const html = render();
expect(html).toContain('data-slot="medication-order-editor"');
expect(html).toContain('data-slot="medication-reorder-section-active"');
expect(html).toContain('data-slot="medication-reorder-section-inactive"');
const lastActive = Math.max(
html.indexOf("Ramipril"),
html.indexOf("Aspirin"),
);
expect(lastActive).toBeLessThan(html.indexOf("Amoxicillin"));
});
it("gathers the injection-sites card (moved from the account profile)", () => {
// v1.18.0 (S5) — injection-site exclusions are a medication setting and
// now live on this screen alongside the list view + order.
const html = render();
expect(html).toContain("Globally excluded injection sites");
});
it("feeds the editor the saved order (active block re-sorted by layout.order)", () => {
// Alphabetical default would put Aspirin first; the saved order pins
// Ramipril to the top — the editor must open showing exactly what
// both /medications views render.
queryState.layout = { version: 1, view: "cards", order: ["a1", "a2"] };
const html = render();
expect(html.indexOf("Ramipril")).toBeLessThan(html.indexOf("Aspirin"));
});
});