Files
WRNexusJS/packages/csr/test/actions.test.ts
T
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

76 lines
2.9 KiB
TypeScript

import { afterEach, beforeEach, expect, test } from "bun:test";
import { Window } from "happy-dom";
import { ACTION_RUNTIME } from "../src/action-runtime.ts";
import { createActionClient } from "../src/actions.ts";
const originalGlobals = new Map(
["window", "document", "location", "CustomEvent", "FormData", "fetch"].map((key) => [
key,
(globalThis as Record<string, unknown>)[key],
]),
);
beforeEach(() => {
for (const key of ["window", "document", "location", "CustomEvent", "FormData", "fetch"]) {
delete (globalThis as Record<string, unknown>)[key];
}
});
afterEach(() => {
for (const [key, value] of originalGlobals) {
if (value === undefined) delete (globalThis as Record<string, unknown>)[key];
else (globalThis as Record<string, unknown>)[key] = value;
}
});
test("action runtime exposes pending, optimistic, success, and invalidation state", async () => {
const win = new Window({ url: "https://example.test/users" });
win.document.cookie = "wire-csrf=token";
win.document.body.innerHTML = `<form data-wrn-action="createUser"><input name="name" value="Ada"></form>`;
const phases: string[] = [];
["optimistic", "pending", "success"].forEach((phase) =>
win.document.addEventListener(`wrnexus:action:${phase}`, () => phases.push(phase)),
);
let invalidated: unknown;
win.addEventListener("wrnexus:cache:invalidate", (event) => {
invalidated = (event as unknown as CustomEvent).detail.tags;
});
const g = globalThis as Record<string, unknown>;
Object.assign(g, {
window: win,
document: win.document,
location: win.location,
CustomEvent: win.CustomEvent,
FormData: win.FormData,
fetch: async () => Response.json({ data: { id: 1 }, invalidated: ["users"] }),
});
(0, eval)(ACTION_RUNTIME);
win.document
.querySelector("form")!
.dispatchEvent(new win.Event("submit", { bubbles: true, cancelable: true }));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(phases).toEqual(["optimistic", "pending", "success"]);
expect(invalidated).toEqual(["users"]);
expect(win.document.querySelector("form")?.getAttribute("data-wrn-action-state")).toBe("success");
});
test("typed action client returns invalidations and structured validation errors", async () => {
const original = globalThis.fetch;
try {
globalThis.fetch = (async () =>
Response.json({ data: { id: 1 }, invalidated: ["users"] })) as unknown as typeof fetch;
const call = createActionClient<{ name: string }, { id: number }>("/users", "createUser");
expect(await call({ name: "Ada" }, { csrfToken: "token" })).toEqual({
data: { id: 1 },
invalidated: ["users"],
});
globalThis.fetch = (async () =>
Response.json({ errors: { name: "Required" } }, { status: 422 })) as unknown as typeof fetch;
await expect(call({ name: "" })).rejects.toMatchObject({
status: 422,
errors: { name: "Required" },
});
} finally {
globalThis.fetch = original;
}
});