import { expect, test } from "bun:test"; import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { iconSizes, SearchIcon } from "@chopin/icons"; let root = import.meta.dir; let repository = join(root, "../../.."); function sourceFiles(directory: string, found: string[] = []): string[] { for (let entry of readdirSync(directory)) { if (entry === "node_modules" || entry === "dist" || entry.startsWith(".")) continue; let path = join(directory, entry); if (statSync(path).isDirectory()) sourceFiles(path, found); else if (entry.endsWith(".tsx") || entry === "package.json") found.push(path); } return found; } test("the sidebar and Chat share one panel-close asset", () => { let sidebar = readFileSync(join(root, "project-sidebar.tsx"), "utf8"); let workspace = readFileSync(join(root, "workspace.tsx"), "utf8"); expect(sidebar).toContain("assets/icons/panel-close.svg"); expect(workspace).toContain("assets/icons/panel-close.svg"); expect(existsSync(join(root, "assets/figma/navigation/collapse.svg"))).toBe(false); expect(existsSync(join(root, "assets/icons/chat-close.svg"))).toBe(false); }); test("the interface uses only the shared Nucleo icon family", () => { let files = sourceFiles(join(repository, "apps")).concat( sourceFiles(join(repository, "packages")), ); for (let file of files) expect(readFileSync(file, "utf8")).not.toContain("@phosphor-icons/react"); }); test("interface icons default to fourteen pixels with compact sidebar controls at twelve", () => { let icon = readFileSync(join(repository, "packages/icons/src/icon.tsx"), "utf8"); let system = readFileSync(join(repository, "packages/icons/src/system.tsx"), "utf8"); expect(icon).toContain("size = 14"); expect(system).toContain("LoaderIcon({ size = 14"); let names = [ "Archive", "ArrowUp", "Check", "Chevron", "Chopin", "CircleClose", "Close", "Document", "Info", "Lightbulb", "Loader", "Lock", "Message", "Plus", "Search", "SignIn", "Siren", "Sparkle", "Warning", ].join("|"); let explicit = new RegExp(`<(${names})Icon\\b[^>]*\\bsize=\\{(\\d+)\\}`, "gs"); let offenders: string[] = []; for ( let file of sourceFiles(join(repository, "apps")).concat( sourceFiles(join(repository, "packages")), ) ) { for (let match of readFileSync(file, "utf8").matchAll(explicit)) { let size = Number(match[2]); if (!(iconSizes as readonly number[]).includes(size)) { offenders.push(`${file}: ${match[0]}`); } } } expect(offenders).toEqual([]); }); test("interface icons are decorative unless explicitly labelled", () => { let decorative = renderToStaticMarkup(createElement(SearchIcon)); let labelled = renderToStaticMarkup(createElement(SearchIcon, { "aria-label": "Search" })); expect(decorative).toContain('aria-hidden="true"'); expect(labelled).not.toContain('aria-hidden="true"'); // A