release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+92 -11
View File
@@ -9,9 +9,13 @@ function mount(html: string): Window {
win.document.body.innerHTML = `<div id="app">${html}</div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(0, eval)(REACTIVE_RUNTIME);
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
// test window may not fire). setupScope is idempotent, so this is safe.
@@ -23,6 +27,9 @@ function mount(html: string): Window {
beforeEach(() => {
delete (globalThis as Record<string, unknown>).window;
delete (globalThis as Record<string, unknown>).document;
delete (globalThis as Record<string, unknown>).location;
delete (globalThis as Record<string, unknown>).fetch;
delete (globalThis as Record<string, unknown>).MutationObserver;
});
test("hydrates {expr} mustaches from data-scope", () => {
@@ -30,6 +37,40 @@ test("hydrates {expr} mustaches from data-scope", () => {
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");
});
test("mounts client-only templates before hydrating their scopes", () => {
const win = mount(
`<div data-wrn-client-root="client-page" aria-busy="true"></div>` +
`<template data-wrn-client-template="client-page"><main data-scope="count: 2"><b>{count}</b></main></template>`,
);
const runtime = win as unknown as {
__wrnexusMountClientRoots?: (root: unknown) => void;
__wrnexusHydrateScopes?: (root: unknown) => void;
};
runtime.__wrnexusMountClientRoots?.(win.document);
runtime.__wrnexusHydrateScopes?.(win.document);
expect(win.document.querySelector("template")).toBeNull();
expect(win.document.querySelector("main b")?.textContent).toBe("2");
});
test("orchestrates named client loads and renders the success template", async () => {
(globalThis as Record<string, unknown>).fetch = () =>
Promise.resolve(Response.json({ data: { name: "Ada" } }));
const win = mount(
`<section data-wrn-async="users" data-wrn-async-retries="0" aria-busy="true">` +
`<div data-wrn-async-content>Loading</div>` +
`<template data-wrn-async-loading>Loading</template>` +
`<template data-wrn-async-success><strong>{users.name}</strong></template>` +
`<template data-wrn-async-error><b>{error.message}</b></template></section>`,
);
const runtime = win as unknown as { __wrnexusHydrateAsyncBoundaries?: (root: unknown) => void };
runtime.__wrnexusHydrateAsyncBoundaries?.(win.document);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(win.document.querySelector("section")?.getAttribute("data-wrn-async-state")).toBe(
"success",
);
expect(win.document.querySelector("strong")?.textContent).toBe("Ada");
});
test("@event (data-on-click) mutates a signal and re-renders", () => {
const win = mount(
`<div data-scope="count: 0"><button data-on-click="count++">{count}</button></div>`,
@@ -83,6 +124,26 @@ test("component functions support formatted multiline assignments and ternaries"
expect(increment.textContent).toBe("0");
});
test("component functions evaluate template literals without unsafe eval", () => {
const behavior = Buffer.from(
JSON.stringify({
functions: `function increment() {
count++
message = \`Count is now \${count}.\`
}`,
watches: [],
lifecycle: {},
}),
).toString("base64");
const win = mount(
`<div data-scope="count: 0, message: 'ready'" data-wrn-behavior="${behavior}">` +
`<button data-on-click="increment()">{message}</button></div>`,
);
const button = win.document.querySelector("button")!;
button.click();
expect(button.textContent).toBe("Count is now 1.");
});
test("declared component events emit through the generic $emit function", () => {
const win = mount(
`<div data-scope="" data-wrn-events="complete">
@@ -220,7 +281,7 @@ test("expression evaluator supports flatMap callbacks", () => {
expect(win.document.querySelector("span")!.textContent).toBe("1,2,3");
});
test("data-show toggles visibility on a reactive expression (tabs pattern)", () => {
test("data-show toggles visibility without destroying interactive state", () => {
const win = mount(
`<div data-scope="tab: 0">
<button data-on-click="tab = 1">go</button>
@@ -228,17 +289,37 @@ test("data-show toggles visibility on a reactive expression (tabs pattern)", ()
<section id="b" data-show="tab === 1">B</section>
</div>`,
);
const disp = (id: string) =>
(win.document.getElementById(id) as unknown as HTMLElement).style.display;
expect(disp("a")).toBe("");
expect(disp("b")).toBe("none");
expect(win.document.getElementById("a")!.getAttribute("data-show")).toBe("true");
expect(win.document.getElementById("b")!.getAttribute("data-show")).toBe("false");
const a = win.document.getElementById("a") as unknown as HTMLElement;
const b = win.document.getElementById("b") as unknown as HTMLElement;
expect(a.style.display).toBe("");
expect(b.style.display).toBe("none");
(win.document.querySelector("button") as unknown as HTMLElement).click();
expect(disp("a")).toBe("none");
expect(disp("b")).toBe("");
expect(win.document.getElementById("a")!.getAttribute("data-show")).toBe("false");
expect(win.document.getElementById("b")!.getAttribute("data-show")).toBe("true");
expect(a.style.display).toBe("none");
expect(b.style.display).toBe("");
});
test("dynamic components remove inactive cases from the live DOM", async () => {
const win = mount(
`<div data-scope="active: 'Admin'">
<button data-on-click="active = active === 'Admin' ? 'Guest' : 'Admin'">switch</button>
<div data-wrn-dynamic-component="Admin" data-wrn-bind-0='["data-wrn-dynamic-component","{active}"]'>
<section data-component-case="Admin">Administrator secret</section>
<section data-component-case="Guest">Guest dashboard</section>
</div>
</div>`,
);
win.document.dispatchEvent(new win.Event("DOMContentLoaded"));
expect(win.document.querySelector('[data-component-case="Admin"]')?.textContent).toContain(
"Administrator",
);
expect(win.document.querySelector('[data-component-case="Guest"]')).toBeNull();
(win.document.querySelector("button") as unknown as HTMLElement).click();
await new Promise((resolve) => setTimeout(resolve, 0));
expect(win.document.querySelector('[data-component-case="Admin"]')).toBeNull();
expect(win.document.querySelector('[data-component-case="Guest"]')?.textContent).toContain(
"Guest",
);
});
test("reactive data attributes preserve explicit boolean strings", () => {