Cover remaining Sass families and fix autocomplete hover
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s

This commit is contained in:
Shinuwa 2026-09-14 13:58:40 +02:00
parent 77f357e056
commit e52d6e66eb
13 changed files with 617 additions and 286 deletions

View file

@ -1,32 +1,33 @@
// Rôle : vérifie les conventions statiques des styles Sass.
import { readFile } from "node:fs/promises";
// 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";
async function readJoinedStyles(paths) {
return (await Promise.all(paths.map((path) => readFile(path, "utf8")))).join("\n");
}
function countLines(source) {
return source.trimEnd().split("\n").length;
}
test("style entrypoint and theme layers are wired", async () => {
const styles = await readFile("website/src/styles/main.scss", "utf8");
const styleNebula = await readFile("website/src/styles/_nebula.scss", "utf8");
const styleTokens = await readFile("website/src/styles/_tokens.scss", "utf8");
const styleBase = await readFile("website/src/styles/_base.scss", "utf8");
const styleMixins = await readFile("website/src/styles/_mixins.scss", "utf8");
const styleShell = await readFile("website/src/styles/_shell.scss", "utf8");
const styleCards = await readFile("website/src/styles/_cards.scss", "utf8");
const styleControls = await readFile("website/src/styles/design-system/_controls.scss", "utf8");
const styleAttributes = await readFile("website/src/styles/design-system/_attributes.scss", "utf8");
const styleControlsPatterns = await readFile("website/src/styles/design-system/controls/_patterns.scss", "utf8");
const styleAttributesPatterns = await readFile("website/src/styles/design-system/attributes/_patterns.scss", "utf8");
const styleControlsButtons = await readFile("website/src/styles/design-system/controls/_buttons.scss", "utf8");
const styleAttributesButtons = await readFile("website/src/styles/design-system/attributes/_buttons.scss", "utf8");
const goalTreeBranch = await readFile("website/src/features/toolboxes/modules/GoalTreeBranch.jsx", "utf8");
const ndsPatternConsumers = await readJoinedStyles([
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",
@ -40,259 +41,14 @@ test("style entrypoint and theme layers are wired", async () => {
"website/src/components/ToolboxModals.jsx",
"website/src/features/toolboxes/modules/AddToolControls.jsx",
"website/src/features/toolboxes/modules/TextImportModal.jsx"
]);
const styleHome = await readJoinedStyles([
"website/src/styles/_home.scss",
"website/src/styles/home/_shared.scss",
"website/src/styles/home/_onboarding.scss",
"website/src/styles/home/_library.scss",
"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/_story.scss",
"website/src/styles/home/_about.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/home/_stats.scss"
]);
const styleToolboxesEntry = await readFile("website/src/styles/_toolboxes.scss", "utf8");
const styleToolboxesCore = await readFile("website/src/styles/toolboxes/_core.scss", "utf8");
const styleToolboxLayout = await readJoinedStyles([
"website/src/styles/toolboxes/_layout.scss",
"website/src/styles/toolboxes/layout/_header.scss",
"website/src/styles/toolboxes/layout/_icon-picker.scss",
"website/src/styles/toolboxes/layout/_cover-editor.scss",
"website/src/styles/toolboxes/layout/_tutorial.scss",
"website/src/styles/toolboxes/layout/_add-tools.scss",
"website/src/styles/toolboxes/layout/_preferences.scss",
"website/src/styles/toolboxes/layout/_modules.scss"
]);
const styleToolboxShell = await readFile("website/src/styles/toolboxes/_module-shell.scss", "utf8");
const styleToolboxTextExchange = await readFile("website/src/styles/toolboxes/_text-exchange.scss", "utf8");
const styleToolboxNotepad = await readFile("website/src/styles/toolboxes/_notepad.scss", "utf8");
const styleToolboxChecklist = await readFile("website/src/styles/toolboxes/_checklist.scss", "utf8");
const styleToolboxCalculator = await readFile("website/src/styles/toolboxes/_calculator.scss", "utf8");
const styleToolboxCalculatorResults = await readFile("website/src/styles/toolboxes/_calculator-results.scss", "utf8");
const styleToolboxTable = await readFile("website/src/styles/toolboxes/_table.scss", "utf8");
const styleToolboxCombos = await readJoinedStyles([
"website/src/styles/toolboxes/_combos.scss",
"website/src/styles/toolboxes/combos/_editor.scss",
"website/src/styles/toolboxes/combos/_palette.scss",
"website/src/styles/toolboxes/combos/_list.scss",
"website/src/styles/toolboxes/combos/_sequence.scss",
"website/src/styles/toolboxes/combos/_tokens.scss",
"website/src/styles/toolboxes/combos/_hold.scss"
]);
const styleToolboxImages = await readFile("website/src/styles/toolboxes/_images.scss", "utf8");
const styleToolboxGoalTree = await readJoinedStyles([
"website/src/styles/toolboxes/_goal-tree.scss",
"website/src/styles/toolboxes/goal-tree/_editor.scss",
"website/src/styles/toolboxes/goal-tree/_view.scss",
"website/src/styles/toolboxes/goal-tree/_nodes.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"
]);
const styleToolboxCalendar = await readJoinedStyles([
"website/src/styles/toolboxes/_calendar.scss",
"website/src/styles/toolboxes/calendar/_base.scss",
"website/src/styles/toolboxes/calendar/_form.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",
"website/src/styles/toolboxes/calendar/_week.scss",
"website/src/styles/toolboxes/calendar/_month.scss",
"website/src/styles/toolboxes/calendar/_responsive.scss"
]);
const styleToolboxSharedControls = await readJoinedStyles([
"website/src/styles/toolboxes/_shared-controls.scss",
"website/src/styles/toolboxes/shared-controls/_selects.scss",
"website/src/styles/toolboxes/shared-controls/_split-controls.scss",
"website/src/styles/toolboxes/shared-controls/_split-tree.scss",
"website/src/styles/toolboxes/shared-controls/_mix-tags.scss",
"website/src/styles/toolboxes/shared-controls/_entries.scss"
]);
const styleOverlays = await readJoinedStyles([
"website/src/styles/_overlays.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"
]);
const styleIcons = await readJoinedStyles([
"website/src/styles/_icons.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"
]);
const styleGames = await readJoinedStyles([
"website/src/styles/_games.scss",
"website/src/styles/games/_home-cards.scss",
"website/src/styles/games/_heading.scss",
"website/src/styles/games/_lists.scss",
"website/src/styles/games/_filters.scss",
"website/src/styles/games/_cards.scss"
]);
const styleMhwilds = await readFile("website/src/styles/_mhwilds.scss", "utf8");
const styleResponsive = await readFile("website/src/styles/_responsive.scss", "utf8");
assert.match(styles, /@use "tokens"/);
assert.match(styles, /@use "nebula"/);
assert.match(styles, /@use "toolboxes"/);
assert.match(styleToolboxesEntry, /@use "toolboxes\/core"/);
assert.match(styleToolboxesCore, /@use "layout"/);
assert.match(styleToolboxesCore, /@use "module-shell"/);
assert.match(styleToolboxesCore, /@use "task-planner"/);
assert.match(styleToolboxesCore, /@use "equipment-planner"/);
assert.match(styleToolboxesCore, /@use "shared-controls"/);
assert.match(styles, /@use "mhwilds"/);
assert.match(styles, /@use "diablo4"/);
assert.match(styleTokens, /--gradient-brand/);
assert.match(styleBase, /:is\(/);
assert.doesNotMatch(styleBase, /\.card-icon-button/);
assert.doesNotMatch(styleBase, /\.drawer-action-button/);
assert.doesNotMatch(styleBase, /\.import-icon-button/);
assert.match(styleBase, /themed-scrollbar/);
assert.match(styleBase, /body\.is-modal-open/);
assert.match(styleNebula, /@function star-field/);
assert.match(styleNebula, /body::after/);
assert.match(styleHome, /\.dialogue-line i/);
assert.match(styleHome, /font-style: italic/);
assert.match(styleHome, /\.about-page/);
assert.doesNotMatch(styleHome, /\.nebula-panel\s*\{/);
assert.match(styleHome, /\.home-onboarding-layout/);
assert.match(styleHome, /\.home-onboarding-slider figcaption/);
assert.match(styleHome, /\.home-onboarding-dots/);
assert.match(styleHome, /\.about-story-section/);
assert.match(styleHome, /\.about-tools-list/);
assert.match(styleHome, /\.about-tool-item \.module-icon/);
assert.match(styleMixins, /@mixin gold-panel-frame/);
assert.match(styleShell, /\.sidebar-about-link/);
assert.match(styleShell, /\.sidebar-mascot/);
assert.match(styleToolboxShell, /\.module-icon-abacus/);
assert.match(styleToolboxShell, /\.module-icon-calculator/);
assert.match(styleToolboxShell, /\.module-icon-columns/);
assert.match(styleToolboxShell, /\.module-icon-table/);
assert.match(styleToolboxCalculator, /\.calculator-module/);
assert.match(styleToolboxTable, /\.table-module/);
assert.match(styleToolboxTable, /\.table-cell-input\.is-selected/);
assert.match(styleToolboxTable, /\.table-cell-input\.is-move-target/);
assert.match(styleToolboxGoalTree, /\.goal-tree-node-card/);
assert.match(styleToolboxGoalTree, /\.goal-tree-node-content-row/);
assert.match(styleToolboxGoalTree, /\.goal-tree-node.is-drop-target/);
assert.match(styleToolboxCalendar, /\.calendar-event-form/);
assert.match(styleToolboxCalendar, /\.calendar-month-date-picker/);
assert.match(styleToolboxCalendar, /\.calendar-alert-mode-switch/);
assert.match(styles, /@use "design-system\/attributes"/);
assert.match(styleToolboxLayout, /\.toolbox-icon-picker/);
assert.match(styleToolboxLayout, /border-radius: inherit/);
assert.match(styleToolboxLayout, /\.toolbox-icon-picker-button\.has-custom-cover img/);
assert.match(styleToolboxLayout, /grid-column: 1 \/ -1/);
assert.match(styleToolboxLayout, /@use "layout\/cover-editor"/);
assert.match(styleToolboxLayout, /\.toolbox-cover-editor/);
assert.match(styleToolboxLayout, /\.toolbox-cover-crop-frame/);
assert.match(styleToolboxLayout, /aspect-ratio: 620 \/ 248/);
assert.match(styleToolboxLayout, /\.toolbox-game-icon/);
assert.match(styleToolboxLayout, /\.toolbox-hero-link/);
assert.match(styleToolboxLayout, /\.toolbox-actions-row/);
assert.match(styleToolboxTextExchange, /\.text-import-actions/);
assert.match(styleToolboxTextExchange, /\.text-import-modal-dialog/);
assert.doesNotMatch(styleToolboxNotepad, /lecture seule/);
assert.match(styleIcons, /ui-icon-drag/);
assert.match(styleIcons, /ui-icon-dropdown/);
assert.match(styleIcons, /ui-icon-enter/);
assert.match(styleIcons, /ui-icon-calculator/);
assert.match(styleIcons, /ui-icon-scrollable/);
assert.match(styleIcons, /ui-icon-hide/);
assert.match(styleIcons, /ui-icon-eye-open/);
assert.match(styleIcons, /ui-icon-eye-closed/);
assert.match(styleIcons, /ui-icon-chevron-down/);
assert.match(styleIcons, /ui-icon-chevron-up/);
assert.match(styleIcons, /ui-icon-flip/);
assert.match(styleIcons, /ui-icon-add/);
assert.match(styleIcons, /ui-icon-add-row/);
assert.match(styleIcons, /ui-icon-add-column/);
assert.match(styleIcons, /ui-icon-remove-row/);
assert.match(styleIcons, /ui-icon-remove-column/);
assert.match(styleIcons, /ui-icon-table/);
assert.match(styleIcons, /ui-icon-map/);
assert.match(styleIcons, /ui-icon-grab/);
assert.match(styleIcons, /ui-icon-shield/);
assert.match(styleIcons, /ui-icon-gun/);
assert.match(styleToolboxShell, /module-icon-chest-armor/);
assert.match(styleToolboxCombos, /\.combo-hold-controls/);
assert.match(styleToolboxCombos, /\.combo-input-direction/);
assert.match(styleToolboxCombos, /\.combo-device-n64\.combo-value-stick-up/);
assert.match(styleToolboxCombos, /\.combo-device-n64\.combo-value-up/);
assert.match(styleToolboxCombos, /\.combo-value-grab \.ui-icon/);
assert.match(styleToolboxChecklist, /\.timer-alert-mode-button/);
assert.match(styleToolboxSharedControls, /\.timer-alert-mode-switch\.is-blocked/);
assert.match(styleToolboxCalculatorResults, /\.calculator-result/);
assert.match(styleToolboxSharedControls, /\.tool-split-scroll-toggle/);
assert.match(styleToolboxImages, /\.annotation-marker/);
assert.match(styleGames, /\.game-home-card/);
assert.match(styleGames, /\.game-layout/);
assert.match(styleGames, /\.game-grid/);
assert.match(styleGames, /\.filter-panel/);
assert.match(styleControls, /@forward "controls\/patterns"/);
assert.match(styleAttributes, /@use "attributes\/patterns"/);
assert.match(styleControlsPatterns, /@mixin panel-surface/);
assert.match(styleControlsPatterns, /@mixin toast-pattern/);
assert.match(styleAttributesPatterns, /\.nds-panel/);
assert.match(styleAttributesPatterns, /\.nds-card/);
assert.match(styleAttributesPatterns, /\.nds-tool-panel/);
assert.match(styleAttributesPatterns, /\.nds-badge/);
assert.match(styleAttributesPatterns, /\.nds-chip/);
assert.match(styleAttributesPatterns, /\.nds-empty-state/);
assert.match(styleAttributesPatterns, /\.nds-inline-notice/);
assert.match(styleAttributesPatterns, /\.nds-progress/);
assert.match(styleAttributesPatterns, /\.nds-filter-panel/);
assert.match(styleAttributesPatterns, /\.nds-modal/);
assert.match(styleAttributesPatterns, /\.nds-drawer-panel/);
assert.match(styleAttributesPatterns, /\.nds-toast/);
assert.match(ndsPatternConsumers, /nds-page-hero/);
assert.match(ndsPatternConsumers, /nds-panel/);
assert.match(ndsPatternConsumers, /nds-card/);
assert.match(ndsPatternConsumers, /nds-card-cover/);
assert.match(ndsPatternConsumers, /nds-tool-panel/);
assert.match(ndsPatternConsumers, /nds-filter-panel/);
assert.match(ndsPatternConsumers, /nds-chip/);
assert.match(ndsPatternConsumers, /nds-badge/);
assert.match(ndsPatternConsumers, /nds-empty-state/);
assert.match(ndsPatternConsumers, /nds-inline-notice/);
assert.match(ndsPatternConsumers, /nds-progress/);
assert.match(ndsPatternConsumers, /nds-modal/);
assert.match(ndsPatternConsumers, /nds-drawer-panel/);
assert.match(ndsPatternConsumers, /nds-toast/);
assert.doesNotMatch(styleCards, /@use "design-system\/surfaces"/);
assert.doesNotMatch(styleCards, /controls\.card-pattern/);
assert.match(styleMhwilds, /\.mhwilds-card/);
assert.match(styleMhwilds, /\.mhwilds-flip-indicator/);
assert.doesNotMatch(styleMhwilds, /\.game-(home-card|layout|grid)/);
assert.doesNotMatch(styleCards, /\.compact-data-card\s*\{/);
assert.doesNotMatch(
[
goalTreeBranch,
styleToolboxGoalTree,
styleControlsButtons,
styleAttributesButtons
].join("\n"),
/(?:data-size="tiny"|icon-button(?:-icon)?\(tiny\)|\$size == tiny)/
);
assert.match(styleOverlays, /overscroll-behavior: contain/);
assert.match(styleResponsive, /@media \(max-width: 760px\)/);
];
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("large sass files stay split by stable responsibility", async () => {