import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import {
MemoryRouter,
useLocation,
useNavigationType,
} from "react-router-dom";
import { EvidenceProvider, useEvidenceSubject } from "./evidenceContext";
import { EvidenceUrlSync } from "./evidenceUrlSync";
import type { ChatTurnResult } from "../types/api";
const MOCK_TURN: ChatTurnResult = {
prompt: "test",
surface: "answer",
articulation_surface: "answer",
walk_surface: "walk",
grounding_source: "teaching",
epistemic_state: "decoded",
normative_clearance: "cleared",
normative_detail: "",
trace_hash: "sha256:abc",
refusal_emitted: false,
hedge_injected: false,
mutation_mode: "runtime_turn",
identity_verdict: null,
safety_verdict: null,
ethics_verdict: null,
proposal_candidates: [],
turn_cost_ms: 10,
checkpoint_emitted: false,
};
function TestConsumer() {
const {
subject,
setSubject,
clearSubject,
inspectorOpen,
toggleInspector,
addressCopyCount,
notifyAddressCopied,
} = useEvidenceSubject();
return (
{subject.kind}
{subject.kind === "proposal" ? subject.proposalId : ""}
{inspectorOpen ? "open" : "closed"}
{addressCopyCount}
);
}
describe("EvidenceContext", () => {
it("starts with kind=none and inspector closed", () => {
render(
,
);
expect(screen.getByTestId("kind")).toHaveTextContent("none");
expect(screen.getByTestId("inspector")).toHaveTextContent("closed");
});
it("setSubject updates kind", () => {
render(
,
);
fireEvent.click(screen.getByText("set-turn"));
expect(screen.getByTestId("kind")).toHaveTextContent("turn");
});
it("clearSubject resets to none", () => {
render(
,
);
fireEvent.click(screen.getByText("set-turn"));
expect(screen.getByTestId("kind")).toHaveTextContent("turn");
fireEvent.click(screen.getByText("clear"));
expect(screen.getByTestId("kind")).toHaveTextContent("none");
});
it("toggleInspector toggles open state", () => {
render(
,
);
expect(screen.getByTestId("inspector")).toHaveTextContent("closed");
fireEvent.click(screen.getByText("toggle"));
expect(screen.getByTestId("inspector")).toHaveTextContent("open");
fireEvent.click(screen.getByText("toggle"));
expect(screen.getByTestId("inspector")).toHaveTextContent("closed");
});
it("throws if used outside provider", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
expect(() => render()).toThrow(
"useEvidenceSubject must be used within EvidenceProvider",
);
spy.mockRestore();
});
it("notifyAddressCopied increments the copy signal", () => {
render(
,
);
expect(screen.getByTestId("copy-count")).toHaveTextContent("0");
fireEvent.click(screen.getByText("notify-copied"));
fireEvent.click(screen.getByText("notify-copied"));
expect(screen.getByTestId("copy-count")).toHaveTextContent("2");
});
});
function LocationProbe() {
const location = useLocation();
const navigationType = useNavigationType();
return (
<>
{location.search}
{navigationType}
>
);
}
function renderWithUrl(initialEntry: string) {
return render(
,
);
}
describe("EvidenceUrlSync", () => {
it("restores the inspector subject and open state from ?inspect=", () => {
renderWithUrl("/proposals/abc?inspect=proposal:abc");
expect(screen.getByTestId("kind")).toHaveTextContent("proposal");
expect(screen.getByTestId("subject-id")).toHaveTextContent("abc");
expect(screen.getByTestId("inspector")).toHaveTextContent("open");
});
it("drops a malformed ?inspect= from the URL and stays closed", async () => {
renderWithUrl("/proposals?inspect=garbage");
expect(screen.getByTestId("kind")).toHaveTextContent("none");
expect(screen.getByTestId("inspector")).toHaveTextContent("closed");
await waitFor(() =>
expect(screen.getByTestId("search")).toHaveTextContent(/^$/),
);
});
it("writes ?inspect= when the inspector opens on a subject, using replace", async () => {
renderWithUrl("/chat");
fireEvent.click(screen.getByText("set-turn"));
fireEvent.click(screen.getByText("toggle"));
await waitFor(() =>
expect(screen.getByTestId("search")).toHaveTextContent("?inspect=turn%3A1"),
);
expect(screen.getByTestId("nav-type")).toHaveTextContent("REPLACE");
});
it("removes ?inspect= when the inspector closes", async () => {
renderWithUrl("/trace/1?inspect=turn:1");
expect(screen.getByTestId("inspector")).toHaveTextContent("open");
fireEvent.click(screen.getByText("toggle"));
await waitFor(() =>
expect(screen.getByTestId("search")).toHaveTextContent(/^$/),
);
});
it("does not write ?inspect= while the inspector stays closed", async () => {
renderWithUrl("/chat");
fireEvent.click(screen.getByText("set-turn"));
await waitFor(() =>
expect(screen.getByTestId("kind")).toHaveTextContent("turn"),
);
expect(screen.getByTestId("search")).toHaveTextContent(/^$/);
});
});