release: WRNexusJS 0.5.12
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
const componentsDir = join(import.meta.dir, "..", "components");
|
||||
|
||||
const replacementComponents = [
|
||||
"Container",
|
||||
"Grid",
|
||||
"Columns",
|
||||
"Card",
|
||||
"Link",
|
||||
"Image",
|
||||
"SearchBox",
|
||||
"Map",
|
||||
"List",
|
||||
"Marquee",
|
||||
"Tabs",
|
||||
"Timeline",
|
||||
"Footer",
|
||||
"Typography",
|
||||
"Divider",
|
||||
] as const;
|
||||
|
||||
const newComponents = [
|
||||
"Section",
|
||||
"SectionHeader",
|
||||
"MarketingSectionHeader",
|
||||
"PageHeader",
|
||||
"TextLink",
|
||||
"FeatureGrid",
|
||||
"FeatureCard",
|
||||
"FeatureIconCard",
|
||||
"Hero",
|
||||
"SplitHero",
|
||||
"HeroActions",
|
||||
"AnnouncementBar",
|
||||
"MetricCard",
|
||||
"MetricGrid",
|
||||
"StatsBar",
|
||||
"CTASection",
|
||||
"BackToTop",
|
||||
"PublicPageShell",
|
||||
] as const;
|
||||
|
||||
const allComponents = [...replacementComponents, ...newComponents];
|
||||
|
||||
function openingTags(source: string): string[] {
|
||||
const tags: string[] = [];
|
||||
let index = 0;
|
||||
|
||||
while (index < source.length) {
|
||||
const start = source.indexOf("<", index);
|
||||
if (start === -1) break;
|
||||
|
||||
let cursor = start + 1;
|
||||
let quote: "'" | '"' | null = null;
|
||||
|
||||
while (cursor < source.length) {
|
||||
const char = source[cursor];
|
||||
if (quote) {
|
||||
if (char === quote) quote = null;
|
||||
} else if (char === "'" || char === '"') {
|
||||
quote = char;
|
||||
} else if (char === ">") {
|
||||
tags.push(source.slice(start, cursor + 1));
|
||||
cursor += 1;
|
||||
break;
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
index = cursor;
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
describe("@wrnexus/ui redesign component pack", () => {
|
||||
test("contains every replacement and new component", async () => {
|
||||
const files = new Set(await readdir(componentsDir));
|
||||
|
||||
for (const name of allComponents) {
|
||||
expect(files.has(`${name}.wrn`)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
for (const name of allComponents) {
|
||||
test(`${name} has a valid component shell`, async () => {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
|
||||
expect(source).toContain(`component ${name} {`);
|
||||
expect(source).toContain("props {");
|
||||
expect(source).toContain("view {");
|
||||
expect(source).toContain(`data-ui-component="${name}"`);
|
||||
expect(source).not.toContain("React");
|
||||
expect(source).not.toContain("useState");
|
||||
expect(source).not.toContain("<script");
|
||||
});
|
||||
}
|
||||
|
||||
test("opening tags do not contain duplicate class attributes", async () => {
|
||||
for (const name of allComponents) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
|
||||
for (const tag of openingTags(source)) {
|
||||
const plainClassAttributes = tag.match(/(?<![:\\w-])class\\s*=/g) ?? [];
|
||||
expect(plainClassAttributes.length).toBeLessThanOrEqual(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("dynamic attribute expressions stay single quoted", async () => {
|
||||
for (const name of allComponents) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
expect(source).not.toMatch(/=\\"\{[^\n]*\}\\"/);
|
||||
}
|
||||
});
|
||||
|
||||
test("existing public defaults remain release compatible", async () => {
|
||||
const expectations: Record<string, string[]> = {
|
||||
Container: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Grid: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Columns: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Link: ['label = "Link"', 'href = "#"', "external = false"],
|
||||
Image: ['src = ""', 'alt = ""', 'loading = "lazy"', "rounded = false"],
|
||||
SearchBox: [
|
||||
'label = "Search Box"',
|
||||
'type = "search"',
|
||||
"disabled = false",
|
||||
"required = false",
|
||||
],
|
||||
Map: ['title = "Map"', "items = []", 'variant = "default"'],
|
||||
List: ['title = "List"', "items = []", 'variant = "default"'],
|
||||
Marquee: ['title = "Marquee"', "items = []", 'variant = "default"'],
|
||||
Tabs: ['label = "Tabs"', "items = []", 'active = ""', 'orientation = "horizontal"'],
|
||||
Timeline: ['title = "Timeline"', "items = []", 'variant = "default"'],
|
||||
Footer: ['label = "Footer navigation"', "items = []", "columns = 3", 'maxWidth = "compact"'],
|
||||
Typography: [
|
||||
'size = "default"',
|
||||
'color = "primary"',
|
||||
"columns = 2",
|
||||
'gap = "md"',
|
||||
'maxWidth = "xl"',
|
||||
],
|
||||
Divider: ['label = ""', 'orientation = "horizontal"'],
|
||||
};
|
||||
|
||||
for (const [name, tokens] of Object.entries(expectations)) {
|
||||
const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
|
||||
for (const token of tokens) expect(source).toContain(token);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user