// Rôle : protège les frontières Sass, les conventions publiques et le découpage des fichiers. import { readFile, readdir } from "node:fs/promises"; import { test } from "node:test"; import assert from "node:assert/strict"; function countLines(source) { return source.trimEnd().split("\n").length; } test("Sass layers retain ownership boundaries and exclude removed APIs", async () => { const root = "website/src/styles"; const paths = (await readdir(root, { recursive: true })).filter((path) => path.endsWith(".scss")); const sources = new Map(await Promise.all(paths.map(async (path) => [path, await readFile(`${root}/${path}`, "utf8")]))); assert.doesNotMatch(sources.get("_base.scss"), /\.(?:card-icon-button|drawer-action-button|import-icon-button)/); assert.doesNotMatch(sources.get("_cards.scss"), /@use "design-system\/surfaces"|controls\.card-pattern|\.compact-data-card\s*\{/); assert.doesNotMatch(sources.get("_mhwilds.scss"), /\.game-(home-card|layout|grid)/); const home = [...sources].filter(([path]) => path === "_home.scss" || path.startsWith("home/")).map(([, source]) => source).join("\n"); assert.doesNotMatch(home, /\.nebula-panel\s*\{/); assert.doesNotMatch(sources.get("toolboxes/_notepad.scss"), /lecture seule/); assert.match(sources.get("design-system/_controls.scss"), /@forward "controls\/patterns"/); assert.match(sources.get("design-system/_attributes.scss"), /@use "attributes\/patterns"/); const branch = await readFile("website/src/features/toolboxes/modules/GoalTreeBranch.jsx", "utf8"); assert.doesNotMatch( [...sources.values(), branch].join("\n"), /(?:data-size="tiny"|icon-button(?:-icon)?\(tiny\)|\$size == tiny)/ ); }); test("product components keep the public NDS pattern vocabulary", async () => { const paths = [ "website/src/pages/HomePage.jsx", "website/src/pages/LibraryPage.jsx", "website/src/pages/AboutPage.jsx", "website/src/features/games/GamesPage.jsx", "website/src/features/games/GameFiltersPanel.jsx", "website/src/features/games/diablo4/Diablo4AffixCard.jsx", "website/src/features/toolboxes/ToolboxCard.jsx", "website/src/features/toolboxes/ToolboxPages.jsx", "website/src/components/AppOverlays.jsx", "website/src/components/StorageQuota.jsx", "website/src/components/ToolboxModals.jsx", "website/src/features/toolboxes/modules/AddToolControls.jsx", "website/src/features/toolboxes/modules/TextImportModal.jsx" ]; const consumers = (await Promise.all(paths.map((path) => readFile(path, "utf8")))).join("\n"); for (const family of [ "page-hero", "panel", "card", "card-cover", "tool-panel", "filter-panel", "chip", "badge", "empty-state", "inline-notice", "progress", "modal", "drawer-panel", "toast" ]) { assert.ok(consumers.includes(`nds-${family}`), `public nds-${family} convention must remain in use`); } }); test("filter patterns expose vertical, compact and menu layouts", async () => { const attributes = await readFile("website/src/styles/design-system/attributes/_patterns.scss", "utf8"); const catalog = await readFile("website/src/pages/DesignSystemPage.jsx", "utf8"); const documentation = await readFile("DESIGN_SYSTEM.md", "utf8"); for (const variant of ["vertical", "horizontal-compact", "horizontal-menu"]) { assert.match(catalog, new RegExp(variant)); assert.match(documentation, new RegExp(variant)); } assert.match(attributes, /nds-filter-menu/); assert.match(attributes, /data-columns="2"/); assert.match(attributes, /grid-template-columns: repeat\(2/); assert.match(attributes, /nds-filter-option-label/); const gameFilters = await readFile("website/src/styles/games/_filters.scss", "utf8"); assert.match(gameFilters, /nds-active-filters/); assert.match(gameFilters, /nds-active-filter/); assert.match(documentation, /composant commun `Tooltip`/); }); test("large sass files stay split by stable responsibility", async () => { const orchestrators = [ "website/src/styles/home/_library.scss", "website/src/styles/home/_about.scss", "website/src/styles/_overlays.scss", "website/src/styles/toolboxes/goal-tree/_nodes.scss", "website/src/styles/_icons.scss", "website/src/styles/toolboxes/calendar/_form.scss" ]; const partials = [ "website/src/styles/home/library/_page.scss", "website/src/styles/home/library/_summary.scss", "website/src/styles/home/library/_toc.scss", "website/src/styles/home/library/_tool-cards.scss", "website/src/styles/home/library/_examples.scss", "website/src/styles/home/library/_previews.scss", "website/src/styles/home/about/_page.scss", "website/src/styles/home/about/_story.scss", "website/src/styles/home/about/_contribute.scss", "website/src/styles/home/about/_tools.scss", "website/src/styles/home/about/_reminders.scss", "website/src/styles/overlays/_image-viewer.scss", "website/src/styles/overlays/_drawers.scss", "website/src/styles/overlays/_modals.scss", "website/src/styles/overlays/_notifications.scss", "website/src/styles/overlays/_tool-add.scss", "website/src/styles/toolboxes/goal-tree/_node-card.scss", "website/src/styles/toolboxes/goal-tree/_node-content.scss", "website/src/styles/toolboxes/goal-tree/_node-actions.scss", "website/src/styles/toolboxes/goal-tree/_node-drag.scss", "website/src/styles/toolboxes/goal-tree/_node-responsive.scss", "website/src/styles/icons/_base.scss", "website/src/styles/icons/_navigation.scss", "website/src/styles/icons/_toolbox.scss", "website/src/styles/icons/_actions.scss", "website/src/styles/icons/_equipment.scss", "website/src/styles/toolboxes/calendar/_form-layout.scss", "website/src/styles/toolboxes/calendar/_date-picker.scss", "website/src/styles/toolboxes/calendar/_event-controls.scss", "website/src/styles/toolboxes/calendar/_form-actions.scss" ]; for (const path of orchestrators) { const source = await readFile(path, "utf8"); assert.ok(source.startsWith("// Rôle :"), `${path} must keep a role header`); assert.ok(countLines(source) <= 120, `${path} should remain an orchestrator`); } for (const path of partials) { const source = await readFile(path, "utf8"); assert.ok(source.startsWith("// Rôle :"), `${path} must keep a role header`); assert.ok(countLines(source) <= 300, `${path} should stay scoped to one responsibility`); } });