import React, { act } from "react"; (globalThis as Record).React = React; import assert from "node:assert/strict"; import { afterEach, it } from "node:test"; import { createRoot, type Root } from "react-dom/client"; import { renderToStaticMarkup } from "react-dom/server"; import { parseHTML } from "linkedom"; import { NotesImportErrors, parseNotesImportResults, postNotesImport, useNotesImport, type NotesImportResult } from "../components/notes-import.tsx"; // Real hook/React state, isolated DOM and wholly stubbed import transport. const dom = parseHTML(""); Object.assign(globalThis, { window: dom.window, document: dom.document, IS_REACT_ACT_ENVIRONMENT: true }); const originalFetch = globalThis.fetch; let root: Root | undefined; let host: HTMLDivElement | undefined; let current: ReturnType; let starts = 0; let finishes = 0; async function mount() { starts = 0; finishes = 0; host = document.createElement("div"); document.body.appendChild(host); root = createRoot(host); function Harness() { current = useNotesImport({ onStart: () => { starts++; }, onFinish: () => { finishes++; } }); return ; } await act(async () => { root!.render(); }); } async function submit(selected: File[] | null = [...files]) { await act(async () => { current.onImportFiles(selected as unknown as FileList | null); }); } afterEach(async () => { await act(async () => { root?.unmount(); }); root = undefined; host?.remove(); host = undefined; globalThis.fetch = originalFetch; }); const files = [new File(["one"], "same.md"), new File(["two"], "same.md")]; const completed = (requestIndex = 0): NotesImportResult => ({ requestIndex, filePath: "same.md", ok: true, outcome: "completed", stats: { nodes: 0, chains: 0 } }); it("maps out-of-order responses by index, not duplicate basenames", () => { const results = [completed(1), { ...completed(0), stats: { nodes: 7 } }]; assert.deepEqual(parseNotesImportResults({ results }, files), results); }); for (const [name, results] of [ ["missing index", [{ ...completed(), requestIndex: undefined }, completed(1)]], ["duplicate index", [completed(), completed()]], ["missing row", [completed()]], ["out-of-range index", [completed(), completed(2)]], ["wrong filename", [{ ...completed(), filePath: "different.md" }, completed(1)]], ["missing stats", [{ ...completed(), stats: undefined }, completed(1)]], ["unknown outcome", [{ ...completed(), outcome: "unchanged" }, completed(1)]], ["conflicting ok", [{ ...completed(), ok: false }, completed(1)]], ["invalid stats", [{ ...completed(), stats: { nodes: "bad" } }, completed(1)]], ["invalid details", [{ ...completed(), slots: [42] }, completed(1)]], ["invalid provider", [{ ...completed(), provider: 42 }, completed(1)]], ["invalid code", [{ ...completed(), code: 42 }, completed(1)]], ["invalid missing", [{ ...completed(), missing: [42] }, completed(1)]], ["invalid error", [{ ...completed(), error: 42 }, completed(1)]], ["missing failure error", [{ ...completed(), ok: false, outcome: "failed" }, completed(1)]], ] as const) it(`malformed response (${name}) is rejected for retry`, () => { assert.throws(() => parseNotesImportResults({ results }, files), /Please retry/); }); it("POST sends only captured files, and retains config/provider details", async () => { const refused: NotesImportResult = { requestIndex: 0, filePath: files[1]!.name, ok: false, outcome: "failed", code: "provider_key_missing", provider: "openai", slots: ["cheap"], error: "No key is set" }; globalThis.fetch = async (url, init) => { assert.equal(url, "/api/graph/import"); assert.equal(init?.method, "POST"); const sent = (init?.body as FormData).getAll("files") as File[]; assert.equal(sent.length, 1); assert.equal(await sent[0]!.text(), "two"); return Response.json({ results: [refused] }); }; assert.deepEqual(await postNotesImport([files[1]!]), [refused]); }); for (const kind of ["network", "non-JSON", "non-2xx", "malformed"] as const) it(`${kind} response rejects rather than false success`, async () => { globalThis.fetch = async () => { if (kind === "network") throw new Error("synthetic offline"); if (kind === "non-JSON") return new Response("not JSON"); if (kind === "non-2xx") return Response.json({ error: "synthetic refused" }, { status: 503 }); return Response.json({ results: [] }); }; await assert.rejects(postNotesImport([files[0]!])); }); for (const outcome of ["completed", "failed", "partial", "aborted", "busy"] as const) it(`${outcome} leaves only escaped raw failure text, without results or controls`, async () => { const file = new File(["notes"], "