feat(claim-drawer): validation rule opens peek with rule catalog
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
|
||||
import React, { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, beforeEach } from "vitest";
|
||||
import { ValidationPanel } from "./ValidationPanel";
|
||||
import { DrillStackProvider } from "@/components/drill/DrillStackProvider";
|
||||
import type { ClaimDetailValidation, ClaimDetailValidationIssue } from "@/types";
|
||||
|
||||
function renderIntoContainer(element: React.ReactElement): {
|
||||
@@ -17,8 +18,11 @@ function renderIntoContainer(element: React.ReactElement): {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const root: Root = createRoot(container);
|
||||
// SP21 Phase 5 Task 5.9: ValidationPanel now uses useDrillStack()
|
||||
// to open validation-rule peeks. Wrap every render in a
|
||||
// DrillStackProvider so the hook has a context to read from.
|
||||
act(() => {
|
||||
root.render(element);
|
||||
root.render(<DrillStackProvider>{element}</DrillStackProvider>);
|
||||
});
|
||||
return {
|
||||
container,
|
||||
@@ -244,4 +248,129 @@ describe("ValidationPanel", () => {
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.9: rule code is drillable (renders as a button)", () => {
|
||||
// The rule code in each IssueGroup now wraps in a button with
|
||||
// data-testid="...-rule-drill". The other rule codes also drill
|
||||
// the same way.
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<ValidationPanel
|
||||
validation={makeValidation({
|
||||
passed: false,
|
||||
errors: [
|
||||
makeIssue({ rule: "R050_diagnosis_present" }),
|
||||
],
|
||||
warnings: [
|
||||
makeIssue({
|
||||
rule: "R200_units_recommended",
|
||||
severity: "warning",
|
||||
}),
|
||||
],
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
const errorRuleDrill = container.querySelector(
|
||||
'[data-testid="validation-errors-rule-drill"]',
|
||||
);
|
||||
const warningRuleDrill = container.querySelector(
|
||||
'[data-testid="validation-warnings-rule-drill"]',
|
||||
);
|
||||
|
||||
expect(errorRuleDrill).not.toBeNull();
|
||||
expect(errorRuleDrill?.tagName).toBe("BUTTON");
|
||||
expect(errorRuleDrill?.textContent).toContain("R050_diagnosis_present");
|
||||
|
||||
expect(warningRuleDrill).not.toBeNull();
|
||||
expect(warningRuleDrill?.tagName).toBe("BUTTON");
|
||||
expect(warningRuleDrill?.textContent).toContain("R200_units_recommended");
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.9: clicking the rule code opens the PeekModal", async () => {
|
||||
// Clicking the rule code in the errors sub-section pushes a
|
||||
// `{ kind: "rule", rule }` peek onto the drill stack. The peek
|
||||
// is a Radix Dialog portal with the eyebrow "Validation rule"
|
||||
// and the rule code as the title.
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<ValidationPanel
|
||||
validation={makeValidation({
|
||||
passed: false,
|
||||
errors: [
|
||||
makeIssue({ rule: "R050_diagnosis_present" }),
|
||||
],
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
const ruleBtn = container.querySelector(
|
||||
'[data-testid="validation-errors-rule-drill"]',
|
||||
) as HTMLButtonElement | null;
|
||||
expect(ruleBtn).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
ruleBtn!.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
// The PeekModal portals to document.body as a Radix dialog. Find
|
||||
// the dialog with the "Validation rule" eyebrow.
|
||||
const dialogs = document.body.querySelectorAll('[role="dialog"]');
|
||||
const peekDialog = Array.from(dialogs).find((d) =>
|
||||
d.textContent?.includes("Validation rule"),
|
||||
);
|
||||
expect(peekDialog).not.toBeNull();
|
||||
// Title is the rule code.
|
||||
expect(peekDialog?.textContent).toContain("R050_diagnosis_present");
|
||||
// Body includes the catalog description for R050.
|
||||
expect(peekDialog?.textContent).toContain("Diagnosis pointer present");
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.9: unknown rule still opens the peek (fallback note)", async () => {
|
||||
// Rules not in the catalog still open the peek — operators
|
||||
// should be able to correlate unknown rule codes to whatever
|
||||
// they were just looking at. The peek shows an "Unknown rule"
|
||||
// note instead of the catalog text.
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<ValidationPanel
|
||||
validation={makeValidation({
|
||||
passed: false,
|
||||
errors: [
|
||||
makeIssue({ rule: "R999_totally_made_up" }),
|
||||
],
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
const ruleBtn = container.querySelector(
|
||||
'[data-testid="validation-errors-rule-drill"]',
|
||||
) as HTMLButtonElement | null;
|
||||
expect(ruleBtn).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
ruleBtn!.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const dialogs = document.body.querySelectorAll('[role="dialog"]');
|
||||
const peekDialog = Array.from(dialogs).find((d) =>
|
||||
d.textContent?.includes("Validation rule"),
|
||||
);
|
||||
expect(peekDialog).not.toBeNull();
|
||||
expect(peekDialog?.textContent).toContain("R999_totally_made_up");
|
||||
expect(peekDialog?.textContent).toContain("Unknown rule");
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// The PeekModal portals to document.body as a Radix dialog. Wipe
|
||||
// any leftover dialogs between tests so the "find dialog by
|
||||
// eyebrow" assertions in the rule-drill tests don't see stale
|
||||
// portals from a prior test.
|
||||
document.body.querySelectorAll('[role="dialog"]').forEach((d) => d.remove());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user