first commit
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { compileWireFile } from "../../../packages/compiler/src/index.ts";
|
||||
|
||||
const root = join(import.meta.dir, "..");
|
||||
const pagesDir = join(root, "app", "pages");
|
||||
const detailPagesDir = join(pagesDir, "components");
|
||||
const reference = JSON.parse(
|
||||
readFileSync(join(root, "..", "..", "packages", "ui", "component-reference.json"), "utf8"),
|
||||
) as {
|
||||
count: number;
|
||||
components: Array<{ mount: string; category: string }>;
|
||||
};
|
||||
|
||||
test("showcase configures local Lucide icon generation", () => {
|
||||
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
||||
devDependencies: Record<string, string>;
|
||||
};
|
||||
const styles = readFileSync(join(root, "app", "styles", "global.css"), "utf8");
|
||||
|
||||
expect(packageJson.devDependencies["@iconify/tailwind4"]).toBeTruthy();
|
||||
expect(packageJson.devDependencies["@iconify-json/lucide"]).toBeTruthy();
|
||||
expect(styles).toContain('@plugin "@iconify/tailwind4";');
|
||||
expect(styles).toContain(".playground-preview-source > .wire-action:not(.w-full)");
|
||||
expect(styles).toContain("width: fit-content;");
|
||||
});
|
||||
|
||||
test("showcase uses a searchable three-column documentation shell", () => {
|
||||
const layout = readFileSync(join(root, "app", "layouts", "showcase.wrn"), "utf8");
|
||||
const styles = readFileSync(join(root, "app", "styles", "global.css"), "utf8");
|
||||
const runtime = readFileSync(join(root, "public", "playground.js"), "utf8");
|
||||
|
||||
expect(layout).toContain("data-docs-directory");
|
||||
expect(layout).toContain("data-docs-search");
|
||||
expect(layout.match(/data-docs-component-link/g)).toHaveLength(reference.count);
|
||||
expect(styles).toContain("grid-template-columns: 17rem minmax(0, 1fr)");
|
||||
expect(runtime).toContain("data-docs-menu-toggle");
|
||||
expect(runtime).toContain('event.key.toLowerCase() === "k"');
|
||||
});
|
||||
|
||||
test("showcase SSR document layout maps persisted design cookies to html attributes", () => {
|
||||
const source = readFileSync(join(root, "app", "layouts", "document.wrn"), "utf8");
|
||||
expect(source).toContain("<html");
|
||||
expect(source).toContain("<head></head>");
|
||||
expect(source).toContain('<div id="app"><slot /></div>');
|
||||
for (const setting of ["style", "palette", "mode", "font", "scale"]) {
|
||||
expect(source).toContain(`data-ui-${setting}`);
|
||||
expect(source).toContain(`cookies['wrn-ui-${setting}']`);
|
||||
}
|
||||
});
|
||||
|
||||
test("advanced select demos use validation schemas and a real remote API", () => {
|
||||
const page = readFileSync(
|
||||
join(root, "app", "pages", "components", "advanced-select.wrn"),
|
||||
"utf8",
|
||||
);
|
||||
expect(page).toContain('data-schema="advanced-select-validation"');
|
||||
expect(page).toContain('data-schema="advanced-select-dynamic-validation"');
|
||||
expect(readFileSync(join(root, "app", "api", "teams.ts"), "utf8")).toContain("export const GET");
|
||||
});
|
||||
|
||||
test("category pages preview every UI component exactly once", () => {
|
||||
const categoryPages = readdirSync(pagesDir)
|
||||
.filter((file) => file !== "index.wrn" && file.endsWith(".wrn"))
|
||||
.map((file) => readFileSync(join(pagesDir, file), "utf8"))
|
||||
.join("\n");
|
||||
|
||||
const mounts = [...categoryPages.matchAll(/<div data-component="([^"]+)"/g)].map(
|
||||
(match) => match[1],
|
||||
);
|
||||
|
||||
expect(mounts).toHaveLength(reference.count);
|
||||
expect(new Set(mounts).size).toBe(reference.count);
|
||||
expect(mounts.sort()).toEqual(reference.components.map((component) => component.mount).sort());
|
||||
});
|
||||
|
||||
test("every component has a detail page with its expected live use cases", () => {
|
||||
const files = readdirSync(detailPagesDir).filter((entry) => entry.endsWith(".wrn"));
|
||||
|
||||
expect(files).toHaveLength(reference.count);
|
||||
|
||||
const detailMounts = files.flatMap((file) => {
|
||||
const source = readFileSync(join(detailPagesDir, file), "utf8");
|
||||
return [...source.matchAll(/<div data-component="([^"]+)"/g)].map((match) => match[1]);
|
||||
});
|
||||
|
||||
// Every detail page has one playground plus three standard demos. Button,
|
||||
// AdvancedSelect, ComboBox, PinInput, and TogglePassword add specialized capability demos.
|
||||
expect(detailMounts).toHaveLength(reference.count * 4 + 83);
|
||||
for (const component of reference.components) {
|
||||
const expectedMounts =
|
||||
component.mount === "Button"
|
||||
? 9
|
||||
: component.mount === "AdvancedSelect"
|
||||
? 42
|
||||
: component.mount === "ComboBox"
|
||||
? 22
|
||||
: component.mount === "PinInput"
|
||||
? 19
|
||||
: component.mount === "TogglePassword"
|
||||
? 11
|
||||
: 4;
|
||||
expect(detailMounts.filter((mount) => mount === component.mount)).toHaveLength(expectedMounts);
|
||||
}
|
||||
}, 15_000);
|
||||
|
||||
test("every detail page has a type-aware realtime playground", () => {
|
||||
const files = readdirSync(detailPagesDir).filter((entry) => entry.endsWith(".wrn"));
|
||||
for (const file of files) {
|
||||
const source = readFileSync(join(detailPagesDir, file), "utf8");
|
||||
expect(source).toContain("data-playground-preview");
|
||||
expect(source).toContain("data-playground-form");
|
||||
expect(source).toContain("data-playground-code");
|
||||
expect(source).toContain("data-playground-copy");
|
||||
expect(source).toContain('href="#playground"');
|
||||
}
|
||||
|
||||
const runtime = readFileSync(join(root, "public", "playground.js"), "utf8");
|
||||
expect(runtime).toContain("fetch(url");
|
||||
expect(runtime).toContain("HTMLSelectElement");
|
||||
expect(runtime).toContain("navigator.clipboard.writeText");
|
||||
expect(runtime).toContain("updateCode(playground)");
|
||||
expect(runtime).toContain("__wrnexusHydrateScopes");
|
||||
expect(runtime).toContain("data-playground-json");
|
||||
expect(runtime).toContain('createPolicy("wrnexus-playground"');
|
||||
expect(runtime).toContain("preview.replaceChildren");
|
||||
expect(runtime).not.toContain("preview.innerHTML");
|
||||
});
|
||||
|
||||
test("detail pages show component syntax beside each live preview", () => {
|
||||
const buttonPage = readFileSync(join(detailPagesDir, "button.wrn"), "utf8");
|
||||
|
||||
expect(buttonPage).toContain('name="pg_label" type="text"');
|
||||
expect(buttonPage).toContain('name="pg_variant"');
|
||||
expect(buttonPage).toContain('<option value="destructive">destructive</option>');
|
||||
expect(buttonPage).not.toContain('<option value="success">success</option></select></label>');
|
||||
expect(buttonPage).toContain('name="pg_color"');
|
||||
expect(buttonPage).toContain('<option value="success">success</option>');
|
||||
expect(buttonPage).toContain('name="pg_size"');
|
||||
expect(buttonPage).toContain('<option value="icon-lg">icon-lg</option>');
|
||||
expect(buttonPage.match(/class="demo-workbench"/g)).toHaveLength(8);
|
||||
expect(buttonPage.match(/<Button/g)).toHaveLength(9);
|
||||
expect(buttonPage).not.toContain("<div data-component="Button"");
|
||||
expect(buttonPage).not.toContain('<details class="demo-code">');
|
||||
});
|
||||
|
||||
test("advanced select and combobox document their complete event contract", () => {
|
||||
for (const slug of ["advanced-select", "combo-box"]) {
|
||||
const source = readFileSync(join(detailPagesDir, `${slug}.wrn`), "utf8");
|
||||
for (const event of ["search", "select", "change", "clear", "open", "close", "load", "error"]) {
|
||||
expect(source).toContain(`<code>@${event}</code>`);
|
||||
}
|
||||
expect(source).toContain('id="events"');
|
||||
expect(source).toContain("event.detail");
|
||||
}
|
||||
});
|
||||
|
||||
test("pin input documents verification events", () => {
|
||||
const source = readFileSync(join(detailPagesDir, "pin-input.wrn"), "utf8");
|
||||
for (const event of ["input", "change", "complete", "paste", "clear", "error"]) {
|
||||
expect(source).toContain(`<code>@${event}</code>`);
|
||||
}
|
||||
expect(source).toContain('id="events"');
|
||||
expect(source).toContain("event.detail");
|
||||
expect(source).toContain("Different lengths");
|
||||
expect(source).toContain('length="3"');
|
||||
expect(source).toContain('length="5"');
|
||||
expect(source).toContain('length="7"');
|
||||
});
|
||||
|
||||
test("toggle password documents visibility states, validation, and declared events", () => {
|
||||
const source = readFileSync(join(detailPagesDir, "toggle-password.wrn"), "utf8");
|
||||
for (const event of ["input", "change", "toggle"]) {
|
||||
expect(source).toContain(`<code>@${event}</code>`);
|
||||
}
|
||||
expect(source).toContain("Checkbox-controlled toggle");
|
||||
expect(source).toContain("Synchronized password fields");
|
||||
expect(source).toContain("Without visibility toggle");
|
||||
expect(source).toContain("fields='[\n {");
|
||||
expect(source).toContain('"label": "New password"');
|
||||
expect(source).toContain('"name": "current-password"');
|
||||
expect(source).not.toContain("fields='[..]'");
|
||||
expect(source).toContain('data-schema="toggle-password-validation"');
|
||||
});
|
||||
|
||||
test("strong password documents live scoring, requirements, popover, and custom characters", () => {
|
||||
const source = readFileSync(join(detailPagesDir, "strong-password.wrn"), "utf8");
|
||||
expect(source).toContain("Live strength meter");
|
||||
expect(source).toContain("Requirements and hint text");
|
||||
expect(source).toContain("Requirements in a popover");
|
||||
expect(source).toContain("Custom special characters");
|
||||
expect(source).toContain('presentation="popover"');
|
||||
expect(source).toContain('specialCharactersSet="@#_-."');
|
||||
for (const event of ["input", "change", "strength"]) {
|
||||
expect(source).toContain(`<code>@${event}</code>`);
|
||||
}
|
||||
});
|
||||
|
||||
test("complex component props are shown as complete readable multiline values", () => {
|
||||
for (const slug of ["advanced-select", "combo-box", "select", "toggle-password"]) {
|
||||
const source = readFileSync(join(detailPagesDir, `${slug}.wrn`), "utf8");
|
||||
expect(source).not.toMatch(/\b(?:options|groups|items|fields)='\[\.\.\.\]'/);
|
||||
}
|
||||
|
||||
const advancedSelect = readFileSync(join(detailPagesDir, "advanced-select.wrn"), "utf8");
|
||||
expect(advancedSelect).toContain("options='[\n {");
|
||||
expect(advancedSelect).toContain('"value": "design"');
|
||||
});
|
||||
|
||||
test("code examples preserve literal object braces after client hydration", () => {
|
||||
const source = readFileSync(join(detailPagesDir, "toggle-password.wrn"), "utf8");
|
||||
expect(source).toContain("{");
|
||||
expect(source).toContain("}");
|
||||
expect(source).not.toContain('fields=\'[\n {\n "label"');
|
||||
});
|
||||
|
||||
test("every generated showcase page compiles", () => {
|
||||
const paths = [
|
||||
...readdirSync(pagesDir)
|
||||
.filter((entry) => entry.endsWith(".wrn"))
|
||||
.map((entry) => join(pagesDir, entry)),
|
||||
...readdirSync(detailPagesDir)
|
||||
.filter((entry) => entry.endsWith(".wrn"))
|
||||
.map((entry) => join(detailPagesDir, entry)),
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
expect(() => compileWireFile(readFileSync(path, "utf8"), path)).not.toThrow();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user