release: WRNexusJS 0.4.0

This commit is contained in:
2026-07-27 12:42:18 +05:30
parent 8b728a3e5d
commit 30e5721e84
250 changed files with 10065 additions and 3923 deletions
+26 -27
View File
@@ -3,14 +3,14 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { compileWireFile } from "../../compiler/src/index.ts";
import { mountHtml, renderComponent } from "../../test/src/index.ts";
import { uiComponentNames, uiComponentsDir, uiCss } from "../src/index.ts";
import { uiComponentNames, uiComponentsDir, uiComponentPath, uiCss } from "../src/index.ts";
test("bundled UI assets are discoverable and readable", () => {
expect(uiComponentNames()).toContain("Button");
expect(uiComponentNames()).toContain("Accordion");
expect(uiComponentNames()).toContain("DataTable");
expect(uiComponentNames()).toContain("WysiwygEditor");
expect(readFileSync(join(uiComponentsDir(), "Button.wrn"), "utf8")).toContain("component Button");
expect(readFileSync(uiComponentPath("Button"), "utf8")).toContain("component Button");
expect(uiCss()).toContain("--wire-");
});
@@ -46,7 +46,7 @@ test("bundled components do not duplicate another component implementation", ()
const implementations = new Map<string, string>();
for (const name of uiComponentNames()) {
const source = readFileSync(join(uiComponentsDir(), `${name}.wrn`), "utf8")
const source = readFileSync(uiComponentPath(name), "utf8")
.replace(/^component\s+[A-Za-z0-9_]+/, "component __NAME__")
.replace(/\r\n/g, "\n")
.trim();
@@ -142,7 +142,7 @@ test("component-system CSS includes responsive, theme-token, focus, and reduced-
test("component boundaries have no default margins and expose the canonical class prop", () => {
for (const name of uiComponentNames()) {
const source = readFileSync(join(uiComponentsDir(), `${name}.wrn`), "utf8");
const source = readFileSync(uiComponentPath(name), "utf8");
const root = source.match(/view\s*\{\s*<[A-Za-z][^>]*>/)?.[0] ?? "";
const classes = root.match(/\bclass="([^"]*)"/)?.[1]?.split(/\s+/) ?? [];
expect(classes.filter((token) => /^-?m[trblxy]?-/.test(token))).toEqual([]);
@@ -154,7 +154,7 @@ test("component boundaries have no default margins and expose the canonical clas
test("every component exposes universal color and size configuration", () => {
for (const name of uiComponentNames()) {
const source = readFileSync(join(uiComponentsDir(), `${name}.wrn`), "utf8");
const source = readFileSync(uiComponentPath(name), "utf8");
expect(source).toMatch(/^\s+color\s*=/m);
expect(source).toMatch(/^\s+size\s*=/m);
}
@@ -162,7 +162,7 @@ test("every component exposes universal color and size configuration", () => {
// test("component markup keeps user-facing content and data behind props", () => {
// for (const name of uiComponentNames()) {
// const source = readFileSync(join(uiComponentsDir(), `${name}.wrn`), "utf8");
// const source = readFileSync(uiComponentPath(name), "utf8");
// const view = source.slice(source.indexOf("view {"));
// const literalText = [...view.matchAll(/>([^<>{}]*[A-Za-z][^<>{}]*)</g)]
// .map((match) => match[1].trim())
@@ -182,7 +182,7 @@ test("every component exposes universal color and size configuration", () => {
test("layout boundaries do not add default padding", () => {
for (const name of ["Container", "Columns", "Grid", "LayoutSplitter", "Typography"]) {
const source = readFileSync(join(uiComponentsDir(), `${name}.wrn`), "utf8");
const source = readFileSync(uiComponentPath(name), "utf8");
const root = source.match(/view\s*\{\s*<[A-Za-z][^>]*>/)?.[0] ?? "";
const classes = root.match(/\bclass="([^"]*)"/)?.[1]?.split(/\s+/) ?? [];
expect(classes.filter((token) => /^p[trblxy]?-/.test(token))).toEqual([]);
@@ -191,13 +191,13 @@ test("layout boundaries do not add default padding", () => {
test("every bundled UI component compiles", () => {
for (const name of uiComponentNames()) {
const path = join(uiComponentsDir(), `${name}.wrn`);
const path = uiComponentPath(name);
expect(() => compileWireFile(readFileSync(path, "utf8"), path)).not.toThrow();
}
});
test("icon buttons expose their accessible label as a hover and focus tooltip", () => {
const button = readFileSync(join(uiComponentsDir(), "Button.wrn"), "utf8");
const button = readFileSync(uiComponentPath("Button"), "utf8");
expect(button).toContain('class="wire-btn__tooltip"');
expect(button).toContain("{ariaLabel || label}");
expect(uiCss()).toContain(".wire-btn:hover > .wire-btn__tooltip");
@@ -205,7 +205,7 @@ test("icon buttons expose their accessible label as a hover and focus tooltip",
});
test("button links navigate by default and only download when the native attribute is passed", () => {
const button = readFileSync(join(uiComponentsDir(), "Button.wrn"), "utf8");
const button = readFileSync(uiComponentPath("Button"), "utf8");
expect(button).not.toMatch(/^\s+download\s*=/m);
expect(button).not.toContain('download="{download}"');
expect(button).toContain("{...attrs}");
@@ -213,14 +213,14 @@ test("button links navigate by default and only download when the native attribu
test("every bundled UI component forwards undeclared native attributes", () => {
for (const name of uiComponentNames()) {
const path = join(uiComponentsDir(), `${name}.wrn`);
const path = uiComponentPath(name);
const output = compileWireFile(readFileSync(path, "utf8"), path);
expect(output).toContain("${__wireSpreadAttrs(__attrs)}");
}
});
test("advanced select opens, filters, and selects without reactive handler errors", async () => {
const source = readFileSync(join(uiComponentsDir(), "AdvancedSelect.wrn"), "utf8");
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
const html = await renderComponent(source, {
name: "team",
options: [
@@ -256,7 +256,7 @@ test("advanced select opens, filters, and selects without reactive handler error
});
test("advanced select clears, restores its placeholder, and keeps multiple counters accurate", async () => {
const source = readFileSync(join(uiComponentsDir(), "AdvancedSelect.wrn"), "utf8");
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
const html = await renderComponent(source, {
name: "team",
multiple: true,
@@ -300,7 +300,7 @@ test("advanced select clears, restores its placeholder, and keeps multiple count
});
test("advanced select closes when a pointer event occurs outside it", async () => {
const source = readFileSync(join(uiComponentsDir(), "AdvancedSelect.wrn"), "utf8");
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
const html = await renderComponent(source, {
name: "team",
options: [{ value: "design", label: "Design" }],
@@ -315,7 +315,7 @@ test("advanced select closes when a pointer event occurs outside it", async () =
});
test("advanced select fetches remote options and appends infinite pages", async () => {
const source = readFileSync(join(uiComponentsDir(), "AdvancedSelect.wrn"), "utf8");
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
const originalFetch = globalThis.fetch;
const requests: string[] = [];
globalThis.fetch = (async (input: string | URL | Request) => {
@@ -394,7 +394,7 @@ test("advanced select fetches remote options and appends infinite pages", async
});
test("combobox filters editable input, selects suggestions, exposes methods, and clears", async () => {
const source = readFileSync(join(uiComponentsDir(), "ComboBox.wrn"), "utf8");
const source = readFileSync(uiComponentPath("ComboBox"), "utf8");
const html = await renderComponent(source, {
name: "team",
placeholder: "Search teams",
@@ -460,7 +460,7 @@ test("combobox filters editable input, selects suggestions, exposes methods, and
});
test("combobox auto-loads remote suggestions and retains its selected value while searching", async () => {
const source = readFileSync(join(uiComponentsDir(), "ComboBox.wrn"), "utf8");
const source = readFileSync(uiComponentPath("ComboBox"), "utf8");
const originalFetch = globalThis.fetch;
const requests: string[] = [];
globalThis.fetch = (async (input: string | URL | Request) => {
@@ -514,7 +514,7 @@ test("combobox auto-loads remote suggestions and retains its selected value whil
});
test("combobox emits search, select, change, clear, open, and close events", async () => {
const source = readFileSync(join(uiComponentsDir(), "ComboBox.wrn"), "utf8");
const source = readFileSync(uiComponentPath("ComboBox"), "utf8");
const html = await renderComponent(source, {
name: "events-team",
options: [
@@ -560,7 +560,7 @@ test("combobox emits search, select, change, clear, open, and close events", asy
});
test("advanced select emits selection events and remote load and error events", async () => {
const source = readFileSync(join(uiComponentsDir(), "AdvancedSelect.wrn"), "utf8");
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
const originalFetch = globalThis.fetch;
const originalConsoleError = console.error;
let rejectRequest = false;
@@ -629,7 +629,7 @@ test("advanced select emits selection events and remote load and error events",
});
test("pin input accepts matching characters, advances focus, navigates backward, and completes", async () => {
const source = readFileSync(join(uiComponentsDir(), "PinInput.wrn"), "utf8");
const source = readFileSync(uiComponentPath("PinInput"), "utf8");
const html = await renderComponent(source, {
name: "verificationCode",
length: 4,
@@ -693,7 +693,7 @@ test("pin input accepts matching characters, advances focus, navigates backward,
});
test("pin input renders four separate cells when length is omitted", async () => {
const source = readFileSync(join(uiComponentsDir(), "PinInput.wrn"), "utf8");
const source = readFileSync(uiComponentPath("PinInput"), "utf8");
const html = await renderComponent(source, {
name: "defaultPin",
});
@@ -703,7 +703,7 @@ test("pin input renders four separate cells when length is omitted", async () =>
});
test("pin input distributes filtered clipboard content and emits paste details", async () => {
const source = readFileSync(join(uiComponentsDir(), "PinInput.wrn"), "utf8");
const source = readFileSync(uiComponentPath("PinInput"), "utf8");
const html = await renderComponent(source, {
name: "emailCode",
length: 6,
@@ -737,7 +737,7 @@ test("pin input distributes filtered clipboard content and emits paste details",
});
test("pin input accepts lowercase alphanumeric entry and normalizes it to the configured pattern", async () => {
const source = readFileSync(join(uiComponentsDir(), "PinInput.wrn"), "utf8");
const source = readFileSync(uiComponentPath("PinInput"), "utf8");
const html = await renderComponent(source, {
name: "securityCode",
length: 4,
@@ -757,7 +757,7 @@ test("pin input accepts lowercase alphanumeric entry and normalizes it to the co
});
test("strong password scores input, supports custom special characters, and opens its popover", async () => {
const source = readFileSync(join(uiComponentsDir(), "StrongPassword.wrn"), "utf8");
const source = readFileSync(uiComponentPath("StrongPassword"), "utf8");
const html = await renderComponent(source, {
name: "newPassword",
presentation: "popover",
@@ -766,7 +766,6 @@ test("strong password scores input, supports custom special characters, and open
const dom = mountHtml(html);
const root = dom.querySelector("[data-wrn-strong-password]") as HTMLElement;
const input = dom.querySelector('input[name="newPassword"]') as HTMLInputElement;
const popover = dom.querySelector(".wire-next__strong-password-popover") as HTMLElement;
const strengthEvents: Array<{ level: string; percent: number }> = [];
root.addEventListener("strength", (event) => {
strengthEvents.push(
@@ -807,7 +806,7 @@ test("strong password scores input, supports custom special characters, and open
});
test("toggle password reveals and conceals its value with accessible declared events", async () => {
const source = readFileSync(join(uiComponentsDir(), "TogglePassword.wrn"), "utf8");
const source = readFileSync(uiComponentPath("TogglePassword"), "utf8");
const html = await renderComponent(source, {
name: "accountPassword",
value: "correct-horse",
@@ -837,7 +836,7 @@ test("toggle password reveals and conceals its value with accessible declared ev
});
test("toggle password supports checkbox control and synchronized password fields", async () => {
const source = readFileSync(join(uiComponentsDir(), "TogglePassword.wrn"), "utf8");
const source = readFileSync(uiComponentPath("TogglePassword"), "utf8");
const synchronizedHtml = await renderComponent(source, {
name: "credentials",
fields: [