diff --git a/src/components/medications/MedicationCardHeader.tsx b/src/components/medications/MedicationCardHeader.tsx index df201dcb2..dc13fd51a 100644 --- a/src/components/medications/MedicationCardHeader.tsx +++ b/src/components/medications/MedicationCardHeader.tsx @@ -48,12 +48,27 @@ export function MedicationCardHeader({ {name} {dose} -
+ {/* D-H6 — state badges (without-notification, paused-since, + inactive) used to share line 2 with the category badge + via `flex flex-wrap`. At 320 px any state badge pushed + the row to three lines and broke the FB-G1 "two-line, + no exceptions" contract for ~20 % of configured drugs. + State badges now ride their own row below the category + badge so the canonical row stays two lines on narrow + viewports. */} +
{categoryLabel} - {stateBadges}
+ {stateBadges ? ( +
+ {stateBadges} +
+ ) : null}
{actions ? (
{actions}
diff --git a/src/components/medications/SideEffectsSection.tsx b/src/components/medications/SideEffectsSection.tsx index 32ba14695..45fdec960 100644 --- a/src/components/medications/SideEffectsSection.tsx +++ b/src/components/medications/SideEffectsSection.tsx @@ -303,7 +303,14 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) {

{fmt.dateShort(new Date(row.occurredAt))} diff --git a/src/components/medications/__tests__/MedicationCardHeader.test.tsx b/src/components/medications/__tests__/MedicationCardHeader.test.tsx new file mode 100644 index 000000000..48a5617e8 --- /dev/null +++ b/src/components/medications/__tests__/MedicationCardHeader.test.tsx @@ -0,0 +1,91 @@ +import { describe, it, expect } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; +import { MedicationCardHeader } from "@/components/medications/MedicationCardHeader"; + +/** + * v1.4.28 — narrow-viewport contract for the shared medication-list + * row primitive (D-H6). + * + * FB-G1 calls for a two-line shape: `{name} {dose}` on line 1 and the + * category badge on line 2. State badges (without-notification, paused, + * inactive) used to share line 2 via `flex flex-wrap`; on a 320 px + * viewport that pushed the row to three lines for ~20 % of configured + * drugs. State badges now ride their own row below the category badge + * so the canonical row stays two lines on narrow viewports. + * + * The tests assert the structural seams, not the visual measurement + * (no DOM measurement layer in SSR). The data-slot on the state-badges + * row plus the absence of state badges inside the category row are the + * load-bearing contracts. + */ + +function render(node: React.ReactNode) { + return renderToStaticMarkup({node}); +} + +describe("", () => { + it("paints `{name} {dose}` on the title row", () => { + const html = render( + , + ); + expect(html).toContain("Ramipril 5 mg"); + expect(html).toContain("Blood Pressure"); + }); + + it("does not paint the state-badges row when no badges are supplied", () => { + const html = render( + , + ); + // Pin the absence so a future refactor can't paint an empty + // wrapper that eats vertical space. + expect(html).not.toContain( + 'data-slot="medication-card-header-state-badges"', + ); + }); + + it("breaks state badges onto their own row below the category badge (D-H6)", () => { + // The narrow-viewport contract: state badges live on their own + // row so the canonical row stays two lines at 320 px even when + // a drug carries one of the state badges. Pinning the seam. + const html = render( + + + Without notification + + + Paused since 12.05 + + + } + />, + ); + expect(html).toContain( + 'data-slot="medication-card-header-state-badges"', + ); + // Both badges land inside the state row, not adjacent to the + // category badge. Verifying via document order: the state-badges + // wrapper must paint AFTER the category badge. + const categoryIdx = html.indexOf("Blood Pressure"); + const stateRowIdx = html.indexOf( + 'data-slot="medication-card-header-state-badges"', + ); + expect(categoryIdx).toBeGreaterThan(-1); + expect(stateRowIdx).toBeGreaterThan(categoryIdx); + }); +}); diff --git a/src/components/medications/__tests__/SideEffectsSection.test.tsx b/src/components/medications/__tests__/SideEffectsSection.test.tsx index a9103a29e..d33b133c9 100644 --- a/src/components/medications/__tests__/SideEffectsSection.test.tsx +++ b/src/components/medications/__tests__/SideEffectsSection.test.tsx @@ -194,6 +194,41 @@ describe(" — timeline rows", () => { expect(html).toContain("Very severe"); }); + it("narrows the date column to w-14 at narrow viewports (D-H7)", () => { + // The earlier `w-[5.5rem]` (88 px) date column overspec pushed + // the category badge + entry label + severity chip's left slot + // into a wrap-prone shape at 320 px. The narrowed `w-14` (56 px) + // fits the longest short-date variant ("15. Mai") with slack + // and recovers 32 px for the free-text notes. + const client = makeClient(); + seedSideEffects(client, "med-1", [ + { + id: "se-1", + category: "GI", + entry: "NAUSEA", + severity: 2, + occurredAt: "2026-05-13T08:00:00.000Z", + notes: null, + }, + ]); + const html = render(, client); + // Class attribute order in SSR depends on JSX prop order; match + // either order so the assertion stays stable across React minor + // versions. + const dateCell = html.match( + /]*data-slot="side-effect-row-date"[^>]*>|]*class="[^"]*w-14[^"]*"[^>]*data-slot="side-effect-row-date"/, + ); + expect(dateCell).not.toBeNull(); + // Source-of-truth check via the rendered HTML — pin both the + // adoption (`w-14`) and the absence (`w-[5.5rem]`). + expect(html).toContain('data-slot="side-effect-row-date"'); + const tagWithSlot = html.match( + /]*data-slot="side-effect-row-date"[^>]*>/, + ); + expect(tagWithSlot?.[0]).toContain("w-14"); + expect(tagWithSlot?.[0]).not.toContain("w-[5.5rem]"); + }); + it("renders German entry and category labels when locale=de", () => { const client = makeClient(); seedSideEffects(client, "med-1", [