feat: sync showcase with WRNexusJS 0.8.72 and @wrnexus/ui 0.8.88
- Sync app, public, scripts, test and examples from the framework's component-showcase: redesigned component demos, marketing blocks under /blocks, template and preview galleries, distinct Timeline/Section variants, Chat Bubble receipts, a top-placement Drawer demo, and the playground code panel fix (objects in array props no longer blank out). - Standalone adaptations kept: the generator and tests read component-reference.json from the published @wrnexus/ui, and tests use the published @wrnexus/compiler and @wrnexus/syntax. - Dependencies: every @wrnexus/* package to its latest release (ui 0.8.88, styles 0.8.26, core 0.8.16, cli 0.8.72, ...), happy-dom for the playground tests, Tailwind 4.3; bun.lock and package-lock.json refreshed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bec9ef2889
commit
c0ac38c572
171 files changed
+21983
-35658
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Window } from "happy-dom";
|
||||
|
||||
test("preview input never requests a replacement; settings still do", async () => {
|
||||
const window = new Window({ url: "http://localhost/components/input" });
|
||||
window.document.body.innerHTML = `<div data-playground data-playground-component="Input">
|
||||
<div data-playground-preview><input aria-label="Example" /></div>
|
||||
<form data-playground-form><input name="label" value="Name" /></form>
|
||||
<pre data-playground-code></pre><span data-playground-status></span>
|
||||
</div>`;
|
||||
let requests = 0;
|
||||
window.fetch = async () => {
|
||||
requests++;
|
||||
return new window.Response("", { status: 500 });
|
||||
};
|
||||
window.eval(readFileSync(new URL("../public/playground.js", import.meta.url), "utf8"));
|
||||
const preview = window.document.querySelector("[data-playground-preview] input")!;
|
||||
preview.dispatchEvent(new window.Event("input", { bubbles: true }));
|
||||
preview.dispatchEvent(new window.Event("change", { bubbles: true }));
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
expect(requests).toBe(0);
|
||||
window.document
|
||||
.querySelector("[data-playground-form] input")!
|
||||
.dispatchEvent(new window.Event("input", { bubbles: true }));
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
expect(requests).toBe(1);
|
||||
await window.happyDOM.close();
|
||||
});
|
||||
|
||||
test("stored theme updates its stylesheet before the theme runtime loads", async () => {
|
||||
const window = new Window({ url: "http://localhost/components/alert" });
|
||||
window.document.cookie = "wrn-ui-mode=light;path=/";
|
||||
window.document.head.innerHTML = '<link rel="stylesheet" data-wrnexus-theme href="/__wrnexus/theme/dark/violet.css">';
|
||||
window.eval(readFileSync(new URL("../public/playground.js", import.meta.url), "utf8"));
|
||||
expect(window.document.documentElement.dataset.theme).toBe("light");
|
||||
expect(window.document.querySelector("link")?.getAttribute("href")).toBe("/__wrnexus/theme/light/violet.css");
|
||||
await window.happyDOM.close();
|
||||
});
|
||||
+20
-36
@@ -1,8 +1,8 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { compileWireFile } from "@wrnexus/compiler";
|
||||
import { parse as parseWireSyntax } from "@wrnexus/syntax";
|
||||
import { compileWrnFile } from "@wrnexus/compiler";
|
||||
import { parse as parseWrnSyntax } from "@wrnexus/syntax";
|
||||
|
||||
const root = join(import.meta.dir, "..");
|
||||
const pagesDir = join(root, "app", "pages");
|
||||
@@ -21,6 +21,8 @@ const reference = JSON.parse(
|
||||
}>;
|
||||
};
|
||||
const manifest = JSON.parse(readFileSync(join(root, "showcase-manifest.json"), "utf8")) as {
|
||||
removed: string[];
|
||||
blocks: Array<{name: string; slug: string; path: string}>;
|
||||
componentCount: number;
|
||||
categoryCount: number;
|
||||
totalDemoCount: number;
|
||||
@@ -63,20 +65,23 @@ test("showcase configures local Lucide icon generation", () => {
|
||||
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(".playground-preview-source > .wrn-action:not(.w-full)");
|
||||
expect(styles).toContain('[data-ui-component="MetricGrid"]');
|
||||
expect(styles).toContain("width: 100%;");
|
||||
});
|
||||
|
||||
test("showcase manifest matches the generated UI reference", () => {
|
||||
expect(manifest.componentCount).toBe(reference.count);
|
||||
expect(manifest.components).toHaveLength(reference.count);
|
||||
expect(manifest.totalDemoCount).toBeGreaterThanOrEqual(reference.count * 3);
|
||||
expect(manifest.componentCount + manifest.blocks.length + manifest.removed.length).toBe(reference.count);
|
||||
expect(manifest.removed).toEqual(["AuthForm", "AuthSplitLayout", "PortalDashboard", "PublicPageShell"]);
|
||||
expect(manifest.blocks.some(block => block.name === "Hero")).toBe(true);
|
||||
expect(manifest.blocks.some(block => block.name === "CTASection")).toBe(true);
|
||||
expect(manifest.components).toHaveLength(manifest.componentCount);
|
||||
expect(manifest.totalDemoCount).toBeGreaterThanOrEqual(manifest.componentCount * 2);
|
||||
expect(new Set(manifest.components.map((component) => component.slug)).size).toBe(
|
||||
reference.count,
|
||||
manifest.componentCount,
|
||||
);
|
||||
|
||||
for (const component of reference.components) {
|
||||
for (const component of reference.components.filter(entry => !manifest.removed.includes(entry.name) && !manifest.blocks.some(block => block.name === entry.name))) {
|
||||
const documented = manifest.components.find((entry) => entry.name === component.name);
|
||||
expect(documented).toBeTruthy();
|
||||
expect(documented?.mount).toBe(component.mount);
|
||||
@@ -92,8 +97,8 @@ test("showcase uses a searchable documentation shell", () => {
|
||||
|
||||
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(layout.match(/data-docs-component-link/g)).toHaveLength(manifest.componentCount);
|
||||
expect(styles).toContain("grid-template-columns: 15rem minmax(0, 1fr)");
|
||||
expect(runtime).toContain("data-docs-menu-toggle");
|
||||
expect(runtime).toContain('event.key.toLowerCase() === "k"');
|
||||
});
|
||||
@@ -115,7 +120,7 @@ test("category pages preview every UI component exactly once", () => {
|
||||
.map((file) => readFileSync(join(pagesDir, file), "utf8"))
|
||||
.join("\n");
|
||||
|
||||
const invalidPreviews = reference.components.flatMap((component) => {
|
||||
const invalidPreviews = reference.components.filter(entry => manifest.components.some(component => component.name === entry.name)).flatMap((component) => {
|
||||
const count = mountCount(categoryPages, component.mount);
|
||||
return count === 1
|
||||
? []
|
||||
@@ -127,7 +132,7 @@ test("category pages preview every UI component exactly once", () => {
|
||||
|
||||
test("every component has a detail page, playground, and manifest-driven demos", () => {
|
||||
const files = readdirSync(detailPagesDir).filter((entry) => entry.endsWith(".wrn"));
|
||||
expect(files).toHaveLength(reference.count);
|
||||
expect(files).toHaveLength(manifest.componentCount);
|
||||
|
||||
for (const component of manifest.components) {
|
||||
const source = detailSource(component.slug);
|
||||
@@ -160,7 +165,7 @@ test("playgrounds preserve typed values, safe DOM replacement, themes, and event
|
||||
expect(runtime).toContain("preview.replaceChildren");
|
||||
expect(runtime).not.toContain("preview.innerHTML");
|
||||
expect(runtime).toContain("__wrnexusHydrateScopes");
|
||||
expect(runtime).toContain("wireTheme?.bind?.(document)");
|
||||
expect(runtime).toContain("wrnTheme?.bind?.(document)");
|
||||
expect(runtime).toContain("bindPlaygroundEvents");
|
||||
expect(runtime).toContain('attributes.push(`${name}="${field.checked}"`)');
|
||||
});
|
||||
@@ -349,27 +354,6 @@ test("generated playground boolean states avoid multiline-sensitive ternaries",
|
||||
}
|
||||
});
|
||||
|
||||
test("getting started and customization guides include code, outcomes, and verification", () => {
|
||||
for (const slug of [
|
||||
"installation",
|
||||
"framework-guides",
|
||||
"accessibility",
|
||||
"resources",
|
||||
"dark-mode",
|
||||
"themes",
|
||||
"colors",
|
||||
"fonts",
|
||||
"sizing",
|
||||
]) {
|
||||
const source = readFileSync(join(pagesDir, `${slug}.wrn`), "utf8");
|
||||
expect(source).toContain("Configure and use it");
|
||||
expect(source).toContain("What changes after this");
|
||||
expect(source).toContain("Verify the result");
|
||||
expect(source).toContain("Expected result");
|
||||
expect(source).toContain("<pre><code>");
|
||||
}
|
||||
});
|
||||
|
||||
test("every generated showcase page compiles and is accepted by the route syntax parser", () => {
|
||||
const paths = [
|
||||
...readdirSync(pagesDir)
|
||||
@@ -382,7 +366,7 @@ test("every generated showcase page compiles and is accepted by the route syntax
|
||||
|
||||
for (const path of paths) {
|
||||
const source = readFileSync(path, "utf8");
|
||||
expect(() => parseWireSyntax(source)).not.toThrow();
|
||||
expect(() => compileWireFile(source, path)).not.toThrow();
|
||||
expect(() => parseWrnSyntax(source)).not.toThrow();
|
||||
expect(() => compileWrnFile(source, path)).not.toThrow();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user