import { act } from "react"; import { createRoot } from "react-dom/client"; import { expect, it, vi } from "vitest"; import type { Spec } from "@json-render/core"; import type { AnswerViewStateRecord, AnswerViewStateWrite } from "../api/answer-view-state"; import { AnswerViewStateScope } from "./answer-view-state-scope"; import { useAnswerViewState } from "./use-answer-view-state"; it("keeps rerenders debounced, flushes a switched session and retains drafts across component remounts", async () => { vi.useFakeTimers(); const root = createRoot(document.createElement("div")); const spec: Spec = { root: "form", elements: { form: { type: "FollowUpForm", children: [], props: { fields: [{ name: "note", kind: "text" }] } } } }; const remote = (session: string): AnswerViewStateRecord => ({ version: "answer-view-state@1", message_id: "message", block_id: "block", revision: 0, schema_digest: "digest", updated_at: null, nodes: { form: { type: "FollowUpForm", values: { note: session } } }, block: { evidenceFilter: "all" } }); const client = { getAnswerViewState: vi.fn(async (session: string) => remote(session)), putAnswerViewState: vi.fn(async (session: string, state: AnswerViewStateWrite) => ({ ...remote(session), revision: state.expected_revision + 1, nodes: state.nodes })) }; const scope = new AnswerViewStateScope("actor:project", client); let current!: ReturnType; function Probe({ session }: { session: string }) { current = useAnswerViewState(scope, session, "message", "block", spec); return null; } try { await act(async () => root.render()); await act(async () => current.update({ ...current.state, nodes: { form: { type: "FollowUpForm", values: { note: "draft" } } } }, ["/nodes/form/values/note"])); await act(async () => root.render()); expect(client.putAnswerViewState).not.toHaveBeenCalled(); await act(async () => root.render()); expect(client.putAnswerViewState.mock.calls[0][0]).toBe("first"); expect(current.state.nodes.form).toMatchObject({ values: { note: "second" } }); await act(async () => root.render()); expect(current.state.nodes.form).toMatchObject({ values: { note: "draft" } }); expect(client.getAnswerViewState).toHaveBeenCalledTimes(2); } finally { await act(async () => root.unmount()); scope.dispose(); vi.useRealTimers(); } });