import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { act, render, screen, within } from "@testing-library/react"; import { setLocale } from "../i18n"; import userEvent from "@testing-library/user-event"; import { http, HttpResponse } from "msw"; import { server } from "../test/msw"; import { EvidenceManagementPage } from "./EvidenceManagementPage"; import type { EvidencePage, EvidenceUnit } from "../api/evidence"; const item: EvidenceUnit = { id: "evidence:order-key", title: "Order key", kind: "domain", language: "en", purposes: ["sql_generation"], applies_to: { concepts: ["Orders"], tables: ["sales.orders"], columns: [] }, payload: { rule: "Join by order number, year and company." }, provenance: { kind: "manual", declared_by: "Curator", original: { source_file: "source/orders.md", supporting_excerpts: ["Old original wording."] } }, review_items: [], file: "curated/domain/order-key.md", status: "modified", revision: "rev" }; const page: EvidencePage = { items: [item], item, total: 1, page: 1, page_size: 25, errors: [], initialized: true, active_revision: "active", pending_revision: null, location: { repository: "/srv/workspaces/repo", workspace: "/srv/workspaces/repo/sales", runtime_workspace: "/data/registry/repo/sales", host: "Installation host", command: "tht workspace evidence consolidate --workspace sales", git_commands: ["git status", "git diff", "git add -A", "git commit", "git push"] } }; function renderPage() { render(); } beforeEach(() => server.use( http.get("/api/workspaces", () => HttpResponse.json([{ id: "sales", displayName: "Sales" }])), http.get("/api/workspaces/sales/evidence", () => HttpResponse.json(page)), http.get("/api/workspaces/sales/evidence/evidence:order-key", () => HttpResponse.json(page)), )); test("copy uses an accessible icon and copies the unmodified full path", async () => { const user = userEvent.setup(); const copy = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue(); renderPage(); await choose(); await user.click(await screen.findByRole("button", { name: "Order key" })); const detail = screen.getByRole("region", { name: "Evidence detail" }); const button = within(detail).getByRole("button", { name: "Copy Evidence file path" }); expect(button).toHaveTextContent(""); expect(button.querySelector("svg")).not.toBeNull(); await user.click(button); expect(copy).toHaveBeenCalledWith("/srv/workspaces/repo/sales/evidence/curated/domain/order-key.md"); expect(within(detail).getByRole("status")).toHaveTextContent("Copied"); copy.mockRejectedValueOnce(new Error("Clipboard unavailable")); await user.click(button); expect(within(detail).getByRole("status")).toHaveTextContent("Select the text and copy it manually."); copy.mockRestore(); }); async function choose() { await screen.findByRole("option", { name: "Sales" }); await userEvent.selectOptions(screen.getByLabelText("Workspace"), "sales"); } test("switches Evidence controls and detail labels without translating archive content", async () => { renderPage(); await choose(); await userEvent.click(await screen.findByRole("button", { name: "Order key" })); await screen.findByText("Join by order number, year and company."); try { act(() => setLocale("it")); expect(screen.getByLabelText("Cerca Evidence")).toBeVisible(); expect(screen.getByRole("button", { name: "Aggiorna file" })).toBeVisible(); expect(screen.getByRole("heading", { name: "Provenienza" })).toBeVisible(); expect(screen.getByText("Join by order number, year and company.")).toBeVisible(); act(() => setLocale("en")); expect(screen.getByLabelText("Search Evidence")).toBeVisible(); expect(screen.getByRole("button", { name: "Refresh files" })).toBeVisible(); } finally { act(() => setLocale("en")); } }); test("reads complete content, manual lineage and actual host file path", async () => { renderPage(); await choose(); await userEvent.click(await screen.findByRole("button", { name: "Order key" })); expect(await screen.findByText("Join by order number, year and company.")).toBeVisible(); expect(screen.getByText("Manual declaration by Curator.")).toBeVisible(); expect(screen.getByText(/Original document \(lineage\)/)).toBeVisible(); expect(screen.getByText("/srv/workspaces/repo/sales/evidence/curated/domain/order-key.md")).toBeVisible(); expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument(); }); test("shows manual consolidation instructions and all kind templates", async () => { renderPage(); await choose(); await userEvent.click(await screen.findByText("Edit files, consolidate and commit")); expect(screen.getByText(page.location.command)).toBeVisible(); await userEvent.click(screen.getByText("Create a manual Evidence file")); await userEvent.selectOptions(screen.getByLabelText("Example kind"), "formula"); expect(screen.getAllByText(/sum\(amount\)/).length).toBeGreaterThan(0); }); test("combines complete-archive filters and refreshes pending state", async () => { server.use(http.get("/api/workspaces/sales/evidence", ({ request }) => { const query = new URL(request.url).searchParams; return HttpResponse.json({ ...page, items: query.get("kind") === "domain" && query.get("q") === "order" ? [item] : [], pending_revision: "pending" }); })); renderPage(); await choose(); await userEvent.type(screen.getByLabelText("Search Evidence"), "order"); await userEvent.selectOptions(screen.getByLabelText("Kind"), "domain"); expect(await screen.findByRole("button", { name: "Order key" })).toBeVisible(); expect(screen.getByText(/Consolidation is incomplete/)).toBeVisible(); }); test("invalid files remain actionable alongside valid units", async () => { server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({ ...page, errors: [{ file: "curated/domain/broken.md", message: "Missing Rule section" }] }))); renderPage(); await choose(); expect(await screen.findByRole("alert")).toHaveTextContent("curated/domain/broken.md"); expect(within(screen.getByRole("region", { name: "Evidence list" })).getByRole("button", { name: "Order key" })).toBeVisible(); }); test("source refresh is explicit and presents current versus proposed content before a decision", async () => { const requests: unknown[] = []; const source = {id: "a".repeat(64), revision: "b".repeat(64), uri: "https://docs.example.test/orders.md", status: "review", availability: "available", current: [item], proposed: [{...item, payload: {rule: "Use the new approved key."}}], removed_ids: []}; server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({...page, source_reviews: [source]})), http.post("/api/workspaces/sales/evidence/sources", async ({request}) => { requests.push(await request.json()); return HttpResponse.json({status: "succeeded", warnings: ["Source decision activated locally."]}); })); renderPage(); await choose(); await screen.findByText(source.uri); expect(requests).toHaveLength(0); await userEvent.click(screen.getByRole("button", {name: "Import or refresh sources"})); await screen.findByText("Source decision activated locally."); expect(requests).toEqual([{action: "refresh"}]); await userEvent.click(screen.getByText(source.uri)); expect(screen.getByText("Current local Evidence")).toBeVisible(); expect(screen.getByText("Use the new approved key.")).toBeVisible(); await userEvent.click(screen.getByRole("button", {name: "Keep local Evidence"})); expect(requests[1]).toEqual({action: "decide", sourceId: source.id, revision: source.revision, decision: "keep"}); }); test("saved source decisions expose retry and preserve the original choice", async () => { const source = {id: "a".repeat(64), revision: "b".repeat(64), uri: "s3://docs/orders.md", status: "applying", decision: "replace", availability: "available", current: [item], proposed: [item], removed_ids: []}; server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({...page, source_reviews: [source]})), http.post("/api/workspaces/sales/evidence/sources", () => HttpResponse.json({status: "failed", warnings: ["Source decision saved, but indexing failed. Retry the same decision."]}))); renderPage(); await choose(); await userEvent.click(await screen.findByText(source.uri)); expect(screen.getByRole("button", {name: "Import or refresh sources"})).toBeDisabled(); expect(screen.queryByRole("button", {name: "Keep local Evidence"})).not.toBeInTheDocument(); await userEvent.click(screen.getByRole("button", {name: "Retry saved decision"})); expect(await screen.findByRole("alert")).toHaveTextContent("Retry the same decision"); });