feat(frontend): j/k row nav + ? cheatsheet on Acks and Remittances

This commit is contained in:
Tyler
2026-06-20 16:57:15 -06:00
parent b980e77cf2
commit 8f419cdeba
6 changed files with 1347 additions and 290 deletions
+276 -33
View File
@@ -75,48 +75,120 @@ async function waitForText(
}
}
/**
* Poll a predicate until it holds or we time out. react-query's fetch
* resolves asynchronously, and Radix portals content into
* `document.body`, so the DOM doesn't reflect new state synchronously
* after a keypress — we have to await a tick before asserting.
*/
async function settle(
predicate: () => boolean,
timeoutMs = 2000
): Promise<void> {
const start = Date.now();
while (!predicate()) {
if (Date.now() - start > timeoutMs) {
throw new Error("settle: predicate did not hold within timeout");
}
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
}
}
/**
* Dispatch a synthetic keydown on `window`. Same pattern used in
* `Claims.test.tsx` and `useDrawerKeyboard.test.ts`.
*/
function pressKey(key: string): void {
act(() => {
window.dispatchEvent(
new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true })
);
});
}
/** The data row at `idx` (per `data-row-index={idx}`). */
function rowAt(idx: number): HTMLTableRowElement | null {
return document.querySelector(
`tbody tr[data-row-index="${idx}"]`
) as HTMLTableRowElement | null;
}
/** True iff exactly one row carries `data-state="selected"`. */
function hasExactlyOneSelectedRow(): boolean {
const selected = document.querySelectorAll(
'tbody tr[data-state="selected"]'
);
return selected.length === 1;
}
/** Fixture used by every test below so the page has rows to navigate. */
const SAMPLE_REMITS = [
{
id: "PCN-1",
claimId: "CLM-1",
payerName: "Colorado Medicaid",
paidAmount: 100,
adjustmentAmount: 60,
receivedDate: "2026-06-19T12:00:00Z",
checkNumber: "",
status: "received",
adjustments: [
{
group: "CO",
reason: "45",
label: "Charge exceeds fee schedule/maximum allowable",
amount: 50,
quantity: null,
},
{
group: "PR",
reason: "1",
label: "Deductible amount",
amount: 10,
quantity: 1,
},
],
},
{
id: "PCN-2",
claimId: "CLM-2",
payerName: "Aetna",
paidAmount: 200,
adjustmentAmount: 0,
receivedDate: "2026-06-18T12:00:00Z",
checkNumber: "",
status: "received",
adjustments: [],
},
{
id: "PCN-3",
claimId: "CLM-3",
payerName: "United",
paidAmount: 300,
adjustmentAmount: 0,
receivedDate: "2026-06-17T12:00:00Z",
checkNumber: "",
status: "received",
adjustments: [],
},
] as const;
describe("Remittances", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders a CAS adjustment label inside the expanded detail row", async () => {
(
api.listRemittances as unknown as ReturnType<typeof vi.fn>
).mockResolvedValue({
items: [
{
id: "PCN-1",
claimId: "CLM-1",
payerName: "Colorado Medicaid",
paidAmount: 100,
adjustmentAmount: 60,
receivedDate: "2026-06-19T12:00:00Z",
checkNumber: "",
status: "received",
adjustments: [
{
group: "CO",
reason: "45",
label: "Charge exceeds fee schedule/maximum allowable",
amount: 50,
quantity: null,
},
{
group: "PR",
reason: "1",
label: "Deductible amount",
amount: 10,
quantity: 1,
},
],
},
],
total: 1,
returned: 1,
items: SAMPLE_REMITS,
total: SAMPLE_REMITS.length,
returned: SAMPLE_REMITS.length,
has_more: false,
});
});
it("renders a CAS adjustment label inside the expanded detail row", async () => {
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-1");
@@ -146,4 +218,175 @@ describe("Remittances", () => {
expect(document.body.textContent).toContain("PR-1");
unmount();
});
it("test_initial_render_shows_no_selection_highlight", async () => {
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// The spec requires the initial render shows no selection
// highlight — none of the three data rows should be marked
// selected.
expect(
document.querySelectorAll('tbody tr[data-state="selected"]').length
).toBe(0);
unmount();
});
it("test_pressing_j_increments_selection_with_wrap", async () => {
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// From null, j lands on row 0.
pressKey("j");
await settle(() => rowAt(0)?.getAttribute("data-state") === "selected");
expect(rowAt(0)?.getAttribute("data-state")).toBe("selected");
// j again → row 1, then row 2.
pressKey("j");
await settle(() => rowAt(1)?.getAttribute("data-state") === "selected");
expect(rowAt(1)?.getAttribute("data-state")).toBe("selected");
pressKey("j");
await settle(() => rowAt(2)?.getAttribute("data-state") === "selected");
expect(rowAt(2)?.getAttribute("data-state")).toBe("selected");
// j from the last row wraps to row 0 (wrap policy documented in
// Remittances.tsx — matches Claims' drawer behavior).
pressKey("j");
await settle(() => rowAt(0)?.getAttribute("data-state") === "selected");
expect(rowAt(0)?.getAttribute("data-state")).toBe("selected");
expect(hasExactlyOneSelectedRow()).toBe(true);
unmount();
});
it("test_pressing_k_decrements_selection_with_wrap", async () => {
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// From null, k jumps to the last row (one-keystroke "bottom of
// list" gesture).
pressKey("k");
await settle(() => rowAt(2)?.getAttribute("data-state") === "selected");
expect(rowAt(2)?.getAttribute("data-state")).toBe("selected");
// k → row 1, then row 0.
pressKey("k");
await settle(() => rowAt(1)?.getAttribute("data-state") === "selected");
expect(rowAt(1)?.getAttribute("data-state")).toBe("selected");
pressKey("k");
await settle(() => rowAt(0)?.getAttribute("data-state") === "selected");
expect(rowAt(0)?.getAttribute("data-state")).toBe("selected");
// k from row 0 wraps to row 2 (last).
pressKey("k");
await settle(() => rowAt(2)?.getAttribute("data-state") === "selected");
expect(rowAt(2)?.getAttribute("data-state")).toBe("selected");
unmount();
});
it("test_j_does_not_trigger_expand_collapse", async () => {
// j/k selection is a focus indicator, NOT a substitute for the
// click-to-expand gesture. Pressing j through a row that has
// adjustments must NOT open the detail row.
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// Row 0 has adjustments — moving selection onto it should leave
// the row collapsed (the user can still click to expand).
pressKey("j");
await settle(() => rowAt(0)?.getAttribute("data-state") === "selected");
expect(document.body.textContent).not.toContain("Adjustments (2)");
pressKey("j");
await settle(() => rowAt(1)?.getAttribute("data-state") === "selected");
expect(document.body.textContent).not.toContain("Adjustments (2)");
unmount();
});
it("test_question_mark_toggles_cheatsheet_and_escape_closes", async () => {
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// No cheatsheet initially.
expect(
document.body.querySelector('[data-testid="keyboard-cheatsheet"]')
).toBeNull();
// `?` opens it.
pressKey("?");
await settle(
() =>
document.body.querySelector('[data-testid="keyboard-cheatsheet"]') !==
null
);
expect(
document.body.querySelector('[data-testid="keyboard-cheatsheet"]')
).not.toBeNull();
// Escape closes it.
pressKey("Escape");
await settle(
() =>
document.body.querySelector('[data-testid="keyboard-cheatsheet"]') ===
null
);
expect(
document.body.querySelector('[data-testid="keyboard-cheatsheet"]')
).toBeNull();
// `?` again re-opens (toggle works in both directions).
pressKey("?");
await settle(
() =>
document.body.querySelector('[data-testid="keyboard-cheatsheet"]') !==
null
);
expect(
document.body.querySelector('[data-testid="keyboard-cheatsheet"]')
).not.toBeNull();
unmount();
});
it("test_j_does_not_navigate_while_cheatsheet_is_open", async () => {
// `enabled: !helpOpen` is the recommended pairing with
// useRowKeyboard: j/k nav yields to the cheatsheet while it's
// open. Pressing j while the cheatsheet is open closes the
// cheatsheet (its own dismiss listener handles any non-`?`
// keypress) but the row should NOT have moved.
const { unmount } = renderIntoContainer(React.createElement(Remittances));
await waitForText("PCN-3");
// Move selection to row 0 first.
pressKey("j");
await settle(() => rowAt(0)?.getAttribute("data-state") === "selected");
// Open cheatsheet.
pressKey("?");
await settle(
() =>
document.body.querySelector('[data-testid="keyboard-cheatsheet"]') !==
null
);
// j while cheatsheet open: closes cheatsheet, row stays at 0.
pressKey("j");
await settle(
() =>
document.body.querySelector('[data-testid="keyboard-cheatsheet"]') ===
null
);
expect(
document.body.querySelector('[data-testid="keyboard-cheatsheet"]')
).toBeNull();
expect(hasExactlyOneSelectedRow()).toBe(true);
expect(rowAt(0)?.getAttribute("data-state")).toBe("selected");
unmount();
});
});