Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions tests/medication-prescribing-workspace.dom.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

import { MedicationPrescribingWorkspace } from "@/components/clinical-dashboard/medication-prescribing-workspace";
import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context";

// The prescribing results view filters a medication catalogue through a
// best/indication/safety/monitoring lens strip. The catalogue hook fetches
// `/api/medications` (and reads the auth session), so it is mocked with a fixed
// set of results chosen to land in different filter buckets; the filter strip is
// the unit under test. Only usePatientProfile needs a real provider (the profile
// stays empty here, so no per-patient alert badges are computed).

type Result = {
id: string;
name: string;
indication: string;
match: string;
dose: string;
ceiling: string;
action: string;
actionTone: "danger" | "warning" | "neutral";
tone: "teal" | "blue" | "slate";
};

// Clozapine: danger + exact fit → best, indication, safety (not monitoring).
const clozapine: Result = {
id: "clozapine",
name: "Clozapine",
indication: "Treatment-resistant schizophrenia",
match: "Exact clinical fit",
dose: "12.5 mg",
ceiling: "900 mg",
action: "Avoid abrupt cessation",
actionTone: "danger",
tone: "teal",
};
// Lithium: warning + monitor language → every filter.
const lithium: Result = {
id: "lithium",
name: "Lithium",
indication: "Bipolar maintenance",
match: "Exact clinical fit",
dose: "400 mg",
ceiling: "1.2 mmol",
action: "Monitor serum levels",
actionTone: "warning",
tone: "blue",
};
// Sertraline: neutral + related match → best only.
const sertraline: Result = {
id: "sertraline",
name: "Sertraline",
indication: "Depression",
match: "Related match",
dose: "50 mg",
ceiling: "200 mg",
action: "First-line option",
actionTone: "neutral",
tone: "slate",
};

// Cross-mode "also matches" strip is a separate AuthProvider-backed component;
// stub it so this test isolates the filter strip from that component's auth deps.
vi.mock("@/components/clinical-dashboard/universal-search-also-matches", () => ({
UniversalSearchAlsoMatches: () => null,
}));

vi.mock("@/components/clinical-dashboard/use-medication-catalog", () => ({
useMedicationCatalog: () => ({
data: {
records: [],
matches: [clozapine, lithium, sertraline].map((result) => ({
medication: undefined,
result,
score: 1,
reasons: [],
})),
total: 3,
governance: {},
},
loading: false,
error: null,
}),
}));
Comment on lines +69 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move the catalog fixtures into vi.hoisted or the mock factory. vi.mock is hoisted, so this factory can hit a TDZ when it reads clozapine, lithium, and sertraline before those module-scoped fixtures are initialized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/medication-prescribing-workspace.dom.test.tsx` around lines 69 - 85,
Update the useMedicationCatalog mock factory to define or obtain clozapine,
lithium, and sertraline within vi.hoisted or directly inside the factory before
constructing matches. Ensure the hoisted vi.mock callback no longer reads those
module-scoped fixtures during their temporal dead zone.


function renderWorkspace() {
return render(
<PatientProfileProvider>
<MedicationPrescribingWorkspace
query="prescribing"
loading={false}
realDataReady
authUnavailable={false}
apiUnavailable={false}
setupWarning={null}
onSuggestedSearch={vi.fn()}
/>
</PatientProfileProvider>,
);
}

// Each result name renders in both the desktop table and the mobile card list,
// so a visible row appears more than once; a filtered-out row appears zero times.
function rowVisible(name: string): boolean {
return screen.queryAllByText(name).length > 0;
}

function filterButton(label: string): HTMLElement {
return screen.getByRole("button", { name: new RegExp(`^${label}`, "i") });
}

afterEach(() => {
vi.restoreAllMocks();
});

describe("MedicationPrescribingWorkspace — result filter strip", () => {
it("labels each lens with the count of matching results", () => {
renderWorkspace();
// best = 3 (all), indication = 2 (exact-fit), safety = 2 (non-neutral), monitor = 1.
expect(filterButton("Best").textContent).toContain("3");
expect(filterButton("Indication").textContent).toContain("2");
expect(filterButton("Safety").textContent).toContain("2");
expect(filterButton("Monitor").textContent).toContain("1");
Comment on lines +121 to +124

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the exact label/count pair.

toContain also passes for incorrect counts such as 13 or 30; match the full button text instead.

Proposed fix
-    expect(filterButton("Best").textContent).toContain("3");
-    expect(filterButton("Indication").textContent).toContain("2");
-    expect(filterButton("Safety").textContent).toContain("2");
-    expect(filterButton("Monitor").textContent).toContain("1");
+    expect(filterButton("Best")).toHaveTextContent(/^Best\s*3$/);
+    expect(filterButton("Indication")).toHaveTextContent(/^Indication\s*2$/);
+    expect(filterButton("Safety")).toHaveTextContent(/^Safety\s*2$/);
+    expect(filterButton("Monitor")).toHaveTextContent(/^Monitor\s*1$/);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(filterButton("Best").textContent).toContain("3");
expect(filterButton("Indication").textContent).toContain("2");
expect(filterButton("Safety").textContent).toContain("2");
expect(filterButton("Monitor").textContent).toContain("1");
expect(filterButton("Best")).toHaveTextContent(/^Best\s*3$/);
expect(filterButton("Indication")).toHaveTextContent(/^Indication\s*2$/);
expect(filterButton("Safety")).toHaveTextContent(/^Safety\s*2$/);
expect(filterButton("Monitor")).toHaveTextContent(/^Monitor\s*1$/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/medication-prescribing-workspace.dom.test.tsx` around lines 121 - 124,
Update the assertions using filterButton for “Best”, “Indication”, “Safety”, and
“Monitor” to compare each button’s complete textContent against the expected
label/count pair, rather than using toContain, so incorrect counts such as 13 or
30 cannot pass.

});

it("defaults to the Best lens with every result shown", () => {
renderWorkspace();
expect(filterButton("Best")).toHaveAttribute("aria-pressed", "true");
expect(filterButton("Safety")).toHaveAttribute("aria-pressed", "false");
expect(rowVisible("Clozapine")).toBe(true);
expect(rowVisible("Lithium")).toBe(true);
expect(rowVisible("Sertraline")).toBe(true);
});

it("narrows to indication-relevant results and drops related-only matches", () => {
renderWorkspace();
fireEvent.click(filterButton("Indication"));

expect(filterButton("Indication")).toHaveAttribute("aria-pressed", "true");
expect(filterButton("Best")).toHaveAttribute("aria-pressed", "false");
expect(rowVisible("Clozapine")).toBe(true);
expect(rowVisible("Lithium")).toBe(true);
// Sertraline is a "Related match", so it leaves the Indication lens.
expect(rowVisible("Sertraline")).toBe(false);
});

it("narrows the Monitor lens to results with monitoring signals only", () => {
renderWorkspace();
fireEvent.click(filterButton("Monitor"));

expect(filterButton("Monitor")).toHaveAttribute("aria-pressed", "true");
expect(rowVisible("Lithium")).toBe(true);
expect(rowVisible("Clozapine")).toBe(false);
expect(rowVisible("Sertraline")).toBe(false);
});
});
Loading