import { expect, test, beforeEach } from "bun:test"; import { Window } from "happy-dom"; import { buildApiRequest } from "@wrnexus/core"; import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts"; import { restoreGlobalsAfterAll } from "./global-restore.ts"; // This test compares the two transports by OUTPUT, not by text. A substring // search over REACTIVE_RUNTIME cannot detect a logic change on either side // (it never touches buildApiRequest at all, and it isn't anchored to the // right line on the runtime side). Only running both implementations on the // same input and diffing what they actually send can catch drift. const REPLACED_GLOBALS = ["window", "document", "location", "fetch", "NodeFilter"]; restoreGlobalsAfterAll(REPLACED_GLOBALS); beforeEach(() => { for (const name of REPLACED_GLOBALS) delete (globalThis as Record)[name]; }); interface RecordedCall { url: string; method: string; body: string | undefined; contentType: string | undefined; } /** Mount the runtime with a recording fetch and run one call through it. */ async function callThroughRuntime( path: string, method: string, input: Record | undefined, ): Promise { const win = new Window() as unknown as Window & Record; win.document.body.innerHTML = `
`; let recorded: RecordedCall | undefined; (globalThis as Record).window = win; (globalThis as Record).document = win.document; (globalThis as Record).location = win.location; (globalThis as Record).NodeFilter = ( win as unknown as { NodeFilter: unknown } ).NodeFilter; (globalThis as Record).fetch = (url: string, init: RequestInit) => { const headers = init.headers as Record; recorded = { url, method: init.method as string, body: init.body as string | undefined, contentType: headers ? headers["content-type"] : undefined, }; return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve({}), }); }; (0, eval)(REACTIVE_RUNTIME); const callApi = (win as unknown as { __wrnexusCallApi: (...args: any[]) => Promise }) .__wrnexusCallApi; await callApi(path, method, input); if (!recorded) throw new Error("runtime did not call fetch"); return recorded; } function callThroughBuilder( path: string, method: string, input: Record | undefined, ): RecordedCall { const built = buildApiRequest(path, method, input); return { url: built.url, method: String(method || "GET").toUpperCase(), body: built.body, contentType: built.contentType, }; } const CASES: Array<{ name: string; path: string; method: string; input: Record | undefined; }> = [ { name: "GET with a mix of present and omitted values", path: "/api/users", method: "GET", input: { name: "Ajay", age: undefined, team: null, note: "" }, }, { name: "GET carrying 0 and false", path: "/api/users", method: "GET", input: { count: 0, active: false }, }, { name: "GET whose values need encoding", path: "/api/users", method: "GET", input: { name: "a b&c" }, }, { name: "POST with a body", path: "/api/users", method: "POST", input: { name: "Ajay" }, }, { name: "POST with no input", path: "/api/users", method: "POST", input: undefined, }, ]; for (const { name, path, method, input } of CASES) { test(`the browser runtime and buildApiRequest agree: ${name}`, async () => { const fromRuntime = await callThroughRuntime(path, method, input); const fromBuilder = callThroughBuilder(path, method, input); expect(fromRuntime.url).toBe(fromBuilder.url); expect(fromRuntime.body).toBe(fromBuilder.body); expect(fromRuntime.contentType).toBe(fromBuilder.contentType); }); }