90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import {
|
|
test,
|
|
expect,
|
|
renderComponent,
|
|
mountHtml,
|
|
callRoute,
|
|
createFetchMock,
|
|
createTestContext,
|
|
} from "../src/index.ts";
|
|
import { runWithRequestContext, renderControlTemplates } from "@wrnexus/core";
|
|
|
|
test("temporary component modules use the same core request context as the caller", async () => {
|
|
await runWithRequestContext({} as never, async () => {
|
|
const html = await renderComponent(
|
|
`component Conditional {\n state open = true\n view { {#if open}<p>Visible</p>{/if} }\n }`,
|
|
);
|
|
expect(html).toContain('data-wrn-if="@wrn-controls-');
|
|
expect(renderControlTemplates()).toContain("Visible");
|
|
});
|
|
});
|
|
|
|
const COUNTER = `component Counter {
|
|
props {
|
|
start = 0
|
|
label = "Count"
|
|
}
|
|
state count = start
|
|
view { <button @click="count++">{label}: {count}</button> }
|
|
}`;
|
|
|
|
test("renderComponent compiles + renders a component with props", async () => {
|
|
const html = await renderComponent(COUNTER, { start: 7, label: "Hits" });
|
|
expect(html).toContain("Hits");
|
|
expect(html).toContain('data-text="count"'); // reactive state span
|
|
expect(html).toContain("7"); // baked initial value
|
|
});
|
|
|
|
test("mountHtml hydrates the reactive runtime for DOM assertions", () => {
|
|
const dom = mountHtml(
|
|
`<div data-scope="items: [{t:'a'},{t:'b'}]"><ul><li data-for="i in items" data-text="i.t"></li></ul></div>`,
|
|
);
|
|
expect(dom.querySelectorAll("li").map((li) => li.textContent)).toEqual(["a", "b"]);
|
|
});
|
|
|
|
test("mountHtml delivers runtime menu events to simulated browser listeners", () => {
|
|
const dom = mountHtml(`
|
|
<nav data-wrn-navbar>
|
|
<div data-wrn-menu data-open="false" class="wrn-navbar__dropdown">
|
|
<button data-wrn-menu-toggle aria-expanded="false">Menu</button>
|
|
</div>
|
|
</nav>
|
|
`);
|
|
const changes: boolean[] = [];
|
|
dom.document.addEventListener("wrn-menu-toggle", (event) => {
|
|
changes.push((event as CustomEvent<{ open: boolean }>).detail.open);
|
|
});
|
|
const button = dom.querySelector("button") as HTMLButtonElement;
|
|
button.click();
|
|
button.click();
|
|
expect(changes).toEqual([true, false]);
|
|
expect(button.getAttribute("aria-expanded")).toBe("false");
|
|
});
|
|
|
|
test("callRoute invokes an API handler with a Context", async () => {
|
|
const handler = (ctx: import("@wrnexus/core").Context) =>
|
|
Response.json({ ok: true, path: ctx.url.pathname, method: ctx.req.method });
|
|
const res = await callRoute(handler, new Request("http://x/api/ping", { method: "POST" }));
|
|
expect(res.status).toBe(200);
|
|
expect(await res.json()).toEqual({ ok: true, path: "/api/ping", method: "POST" });
|
|
});
|
|
|
|
test("createTestContext installs route params, locals and a user without casts", () => {
|
|
const user = { id: "u1" };
|
|
const ctx = createTestContext("/items/42", {
|
|
params: { id: "42" },
|
|
user,
|
|
locals: { trace: "test" },
|
|
});
|
|
expect(ctx.params.id).toBe("42");
|
|
expect(ctx.user).toBe(user);
|
|
expect(ctx.locals.trace).toBe("test");
|
|
});
|
|
|
|
test("createFetchMock is fluent and compatible with Bun fetch", async () => {
|
|
const fetchMock: typeof fetch = createFetchMock().respondOnce(Response.json({ ok: true }));
|
|
const response = await fetchMock("https://example.test/token");
|
|
expect(await response.json()).toEqual({ ok: true });
|
|
expect(typeof fetchMock.preconnect).toBe("function");
|
|
});
|