Implement CE-WP-0010 Annotate & Attributes UX.
Rename Review→Annotate; Capture keeps Evidence and adds Attributes column (no bottom strip); evidence/attribute filters; card→citation connectors via shared Overlay bridges; finish workplan T01–T07.
This commit is contained in:
parent
dc7d8f7ce8
commit
e9f7676a1a
15 changed files with 241 additions and 437 deletions
|
|
@ -57,9 +57,9 @@ describe("Capture session persistence", () => {
|
|||
const user = userEvent.setup();
|
||||
const first = await loadApp();
|
||||
|
||||
const summary = screen.getByLabelText("Summary of the matter");
|
||||
const summary = screen.getByRole("textbox", { name: /Summary of the matter/ });
|
||||
await user.type(summary, "Persisted summary text");
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
|
||||
await waitFor(() => {
|
||||
const stored = Object.values(globalThis.localStorage ?? {}).join("");
|
||||
|
|
@ -70,7 +70,7 @@ describe("Capture session persistence", () => {
|
|||
|
||||
await loadApp();
|
||||
|
||||
const restored = screen.getByLabelText("Summary of the matter") as HTMLTextAreaElement;
|
||||
const restored = screen.getByRole("textbox", { name: /Summary of the matter/ }) as HTMLTextAreaElement;
|
||||
await waitFor(() => {
|
||||
expect(restored.value).toBe("Persisted summary text");
|
||||
});
|
||||
|
|
@ -84,7 +84,7 @@ describe("Capture session persistence", () => {
|
|||
const user = userEvent.setup();
|
||||
await loadApp();
|
||||
|
||||
await user.type(screen.getByLabelText("Disputed amount"), "EUR 500");
|
||||
await user.type(screen.getByRole("textbox", { name: /Disputed amount/ }), "EUR 500");
|
||||
|
||||
await waitFor(() => {
|
||||
const keys = Object.keys(globalThis.localStorage ?? {});
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ describe("FormsApp — active-evidence cycling (CE-WP-0003-T06)", () => {
|
|||
});
|
||||
|
||||
// Focus Summary, then click strip card → link gets created.
|
||||
const summaryField = screen.getByLabelText("Summary of the matter");
|
||||
const summaryField = screen.getByRole("textbox", { name: /Summary of the matter/ });
|
||||
await user.click(summaryField);
|
||||
await user.click(stripCard);
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ describe("FormsApp — active-evidence cycling (CE-WP-0003-T06)", () => {
|
|||
//
|
||||
// Note: clicking the same field doesn't fire onFocus if it's already
|
||||
// focused. Move focus elsewhere first, then back.
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
await user.click(summaryField);
|
||||
|
||||
// The strip card for the linked evidence has aria-current="true".
|
||||
|
|
|
|||
|
|
@ -104,13 +104,22 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
|
|||
await user.selectOptions(screen.getByTestId("field-add-type-select"), "date");
|
||||
await user.click(screen.getByTestId("field-add-save"));
|
||||
|
||||
const hearingInput = await screen.findByLabelText("Hearing date");
|
||||
const hearingInput = await waitFor(() => {
|
||||
const inputs = Array.from(
|
||||
document.querySelectorAll('input[type="date"]'),
|
||||
) as HTMLInputElement[];
|
||||
const match = inputs.find((el) =>
|
||||
(el.labels?.[0]?.textContent ?? "").includes("Hearing date"),
|
||||
);
|
||||
if (!match) throw new Error("expected Hearing date input");
|
||||
return match;
|
||||
});
|
||||
expect(hearingInput.getAttribute("type")).toBe("date");
|
||||
},
|
||||
);
|
||||
|
||||
it(
|
||||
"edits an existing field label and type via the pencil icon",
|
||||
"edits an existing attribute key and type via the pencil icon",
|
||||
{ timeout: 15000 },
|
||||
async () => {
|
||||
const user = userEvent.setup();
|
||||
|
|
@ -130,8 +139,8 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
|
|||
);
|
||||
await user.click(screen.getByTestId("field-edit-summary-save"));
|
||||
|
||||
await screen.findByLabelText("Matter summary");
|
||||
expect(screen.queryByLabelText("Summary of the matter")).toBeNull();
|
||||
await screen.findByRole("textbox", { name: /Matter summary/ });
|
||||
expect(screen.queryByRole("textbox", { name: /Summary of the matter/ })).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -156,7 +165,7 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
|
|||
await user.type(addLabel, "Custom slot");
|
||||
await user.click(screen.getByTestId("field-add-save"));
|
||||
|
||||
const customField = await screen.findByLabelText("Custom slot");
|
||||
const customField = await screen.findByRole("textbox", { name: /Custom slot/ });
|
||||
await user.click(customField);
|
||||
|
||||
const stripCard = screen.getByRole("button", { name: /Link to custom field/ });
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ describe("Capture — field value persistence (CE-WP-0008-T01)", () => {
|
|||
const user = userEvent.setup();
|
||||
await loadApp();
|
||||
|
||||
const summary = screen.getByLabelText("Summary of the matter");
|
||||
const summary = screen.getByRole("textbox", { name: /Summary of the matter/ });
|
||||
await user.type(summary, "Tenant owes arrears");
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
await user.click(summary);
|
||||
|
||||
expect((summary as HTMLTextAreaElement).value).toBe("Tenant owes arrears");
|
||||
|
|
@ -63,10 +63,10 @@ describe("Capture — field value persistence (CE-WP-0008-T01)", () => {
|
|||
const user = userEvent.setup();
|
||||
await loadApp();
|
||||
|
||||
const deadline = screen.getByLabelText("Key deadline") as HTMLInputElement;
|
||||
const deadline = document.querySelector("#field-deadline") as HTMLInputElement;
|
||||
await user.clear(deadline);
|
||||
await user.type(deadline, "2026-12-15");
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
|
||||
expect(deadline.value).toBe("2026-12-15");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ describe("FormsApp — focus-gated linking (CE-WP-0007-T02)", () => {
|
|||
await user.click(screen.getByRole("button", { name: "Capture" }));
|
||||
await screen.findByRole("button", { name: /Field-first link test/ });
|
||||
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
const stripCard = screen.getByRole("button", {
|
||||
name: /Field-first link test/,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -147,22 +147,21 @@ describe("CE-WP-0003-T08 — PRD scenario steps 5-9 end-to-end", () => {
|
|||
// CE-WP-0005: route is now session-scoped.
|
||||
expect(window.location.hash).toMatch(/^#\/s\/sess_[^/]+\/forms\/demo$/);
|
||||
|
||||
// Step 6: focus summary, then click the strip card to link directly.
|
||||
const summaryField = screen.getByLabelText("Summary of the matter");
|
||||
// Step 6: focus summary attribute, then click the Evidence card to link.
|
||||
const summaryField = screen.getByRole("textbox", { name: /Summary of the matter/ });
|
||||
await user.click(summaryField);
|
||||
const stripCard = await screen.findByRole("button", {
|
||||
const evidenceCard = await screen.findByRole("button", {
|
||||
name: /Overlay E2E evidence/,
|
||||
});
|
||||
await user.click(stripCard);
|
||||
await user.click(evidenceCard);
|
||||
|
||||
// Move focus elsewhere and back to re-fire focus on summary so that
|
||||
// ActiveStateProvider triggers focus-target (the previous click that
|
||||
// created the link consumed the staged state).
|
||||
await user.click(screen.getByLabelText("Disputed amount"));
|
||||
await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
|
||||
await user.click(summaryField);
|
||||
|
||||
// Step 8: aria-current on field row, chip, and (via the active
|
||||
// state) the strip card.
|
||||
// Step 8: aria-current on attribute row and active evidence card.
|
||||
await waitFor(() => {
|
||||
const fieldRow = document.querySelector(
|
||||
'[data-field-id="summary"][aria-current="true"]',
|
||||
|
|
@ -173,7 +172,7 @@ describe("CE-WP-0003-T08 — PRD scenario steps 5-9 end-to-end", () => {
|
|||
expect(activeCard).not.toBeNull();
|
||||
expect(activeCard!.textContent).toMatch(/Overlay E2E evidence/);
|
||||
|
||||
// Step 9: SVG overlay renders 2 paths (field→card + card→highlight).
|
||||
// Step 9: SVG overlay renders 2 paths (attribute→card + card→highlight).
|
||||
// HighlightRectBridge registers via the mocked getHighlightClientRects.
|
||||
await waitFor(() => {
|
||||
const svg = document.querySelector('[data-testid="visual-guide-overlay"]');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/**
|
||||
* CE-WP-0006-T04 — evidence strip filter (all vs attached-to-active-field).
|
||||
* CE-WP-0010-T03 — Evidence column text filter (≥3 characters).
|
||||
* Replaces the CE-WP-0006 bottom-strip All/Attached toggles (strip removed).
|
||||
*/
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
|
@ -70,7 +71,7 @@ async function captureAndSave(user: ReturnType<typeof userEvent.setup>, commenta
|
|||
await screen.findByText(new RegExp(commentary));
|
||||
}
|
||||
|
||||
describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
|
||||
describe("EvidenceSidebar — text filter (CE-WP-0010-T03)", () => {
|
||||
beforeEach(() => {
|
||||
viewerSnapshot.onSelectionCaptured = null;
|
||||
globalThis.localStorage?.clear();
|
||||
|
|
@ -78,7 +79,7 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
|
|||
history.replaceState(null, "", window.location.pathname);
|
||||
}
|
||||
seedSessionWithDoc({
|
||||
sessionName: "T04-filter",
|
||||
sessionName: "T03-filter",
|
||||
documentTitle: FIXTURE.filename,
|
||||
canonicalText: SYNTHETIC_CANONICAL,
|
||||
});
|
||||
|
|
@ -90,7 +91,7 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
|
|||
});
|
||||
|
||||
it(
|
||||
"narrows to attached evidence on field focus and restores on All toggle",
|
||||
"filters evidence cards only when the query has 3+ characters",
|
||||
{ timeout: 20000 },
|
||||
async () => {
|
||||
const user = userEvent.setup();
|
||||
|
|
@ -99,26 +100,20 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
|
|||
await captureAndSave(user, "Linked to summary");
|
||||
await captureAndSave(user, "Unlinked orphan");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Capture" }));
|
||||
const linkedCard = await screen.findByRole("button", {
|
||||
name: /Linked to summary/,
|
||||
});
|
||||
expect(screen.getByRole("button", { name: /Linked to summary/ })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
|
||||
|
||||
// Link the first item to summary; field focus keeps attached filter active.
|
||||
await user.click(screen.getByLabelText("Summary of the matter"));
|
||||
await user.click(linkedCard);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("field-summary-chip").textContent).toMatch(
|
||||
/1 evidence/,
|
||||
);
|
||||
expect(screen.queryByRole("button", { name: /Unlinked orphan/ })).toBeNull();
|
||||
});
|
||||
const filter = screen.getByTestId("evidence-filter");
|
||||
await user.type(filter, "Un");
|
||||
// Under 3 chars — both still visible
|
||||
expect(screen.getByRole("button", { name: /Linked to summary/ })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "All" }));
|
||||
await user.type(filter, "l"); // "Unl"
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("button", { name: /Linked to summary/ })).toBeNull();
|
||||
expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue