release: WRNexusJS 0.4.0
This commit is contained in:
+26
-27
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user