From 5f1c4aefe97d4f1b337ea1f99269f0c009b52fce Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 06:21:02 +0000 Subject: [PATCH 1/2] docs(tests): docs and test hygiene remainder (SL-014, 028, 031, 032, 033) - SL-014: add the 12 previously-undocumented npm scripts to CLAUDE.md's Key scripts table, prioritizing the CI-gate scripts (check:macros, check:registry, audit:check, lint:css). - SL-028: add tests/README.md documenting the *.spec.js (Playwright) vs *.test.js (node:test) convention, referencing SL-027 as the motivating example of what happens when it's unenforced. - SL-031: retitle docs/migration.md's stale "0.6.25 -> next" header to "0.6.25 -> 0.6.26" (traced via CHANGELOG.md to the release that actually shipped the base-alias removal it documents), matching the versioned-header style used elsewhere in the same file. - SL-032: document the tier1-p2/p7/p8/p10 numbering gaps (no P1, P3-P6, P9 files) in tests/README.md plus a one-line pointer in each of the 4 files - they trace back to the tier-1-color-fallback feature's original property enumeration; not renumbered. - SL-033: extract tests/render-helpers.js (BUNDLE path + renderWithBundle helper) after diffing all 8 page.setContent call sites individually - 5 files (a11y-patterns, layout, macros, states-full, typography) had near-identical setup() functions differing only in viewport/margin/ extra-stylesheet, now parameterized through one shared function; layers.spec.js/print.spec.js/container-queries.spec.js only shared the BUNDLE path constant (their setContent bodies are genuinely per-test or differently-shaped) and were left otherwise untouched, per the plan's explicit caution not to force uniformity where the originals differ. Verified with real browser execution (not just --list): all 5 refactored setup()-using spec files plus layers/print/container-queries ran under a temporary local executablePath override (this sandbox's pinned Playwright browser build isn't pre-installed) - 150/150 tests pass unchanged. lint:css, build, test:unit (31/31), check:llm-guide, check:version all pass; the temporary config was not committed. --- CLAUDE.md | 14 ++++++++++- docs/migration.md | 2 +- tests/README.md | 28 ++++++++++++++++++++++ tests/a11y-patterns.spec.js | 9 ++----- tests/container-queries.spec.js | 4 +--- tests/layers.spec.js | 14 +++++------ tests/layout.spec.js | 8 ++----- tests/macros.spec.js | 10 ++++---- tests/print.spec.js | 8 +++---- tests/render-helpers.js | 40 ++++++++++++++++++++++++++++++++ tests/states-full.spec.js | 9 ++----- tests/tier1-p10-contrast.test.js | 3 +++ tests/tier1-p2-coverage.test.js | 3 +++ tests/tier1-p7-oldengine.test.js | 3 +++ tests/tier1-p8-modern.spec.js | 3 +++ tests/typography.spec.js | 6 ++--- 16 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/render-helpers.js diff --git a/CLAUDE.md b/CLAUDE.md index 1e6729f9..925472aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,9 +51,21 @@ requires a rebuild+redeploy, not just a file edit. | `npm run version-sync` | Sync all version references to root `package.json` | | `npm run check:version` | Verify all version references match (CI gate — run before every commit that touches versions) | | `npm run check:llm-guide` | Verify `docs/llm-guide.md` only references live tokens (CI gate) | +| `npm run check:macros` | Verify `.sf-*` macro classes match `docs/macros.md` (CI gate) | +| `npm run check:registry` | Verify `token-registry.json` is in sync with source (CI gate) | +| `npm run audit:check` | Verify `docs/registry.json` matches source without writing (CI gate) | +| `npm run lint:css` | Lint all CSS source with stylelint (CI gate) | +| `npm run lint:css:fix` | Lint CSS source and auto-fix violations | | `npm run docs` | Regenerate docs and sync configurator API index | +| `npm run docs:tokens` | Regenerate `docs/tokens.md` only | +| `npm run docs:index` | Regenerate `docs/token-index.md` + `.json` only | +| `npm run docs:api` | Regenerate `docs/api-index.md` + `.json` only | +| `npm run docs:classes` | Regenerate `docs/classes.md` only | +| `npm run gen:registry` | Regenerate `token-registry.json` (stable id assignments) | | `npm run configurator:sync` | Push `docs/api-index.json` → `configurator/src/data/api-index.generated.json` | -| `npm run audit` | Audit CSS tokens for consistency | +| `npm run audit` | Audit CSS tokens for consistency, writing `docs/registry.json` | +| `npm run watch` | Rebuild CSS bundles on file change (dev watch mode) | +| `npm run test:unit` | Run the Node `--test` unit suite only, without the Playwright e2e build | | `npm test` | Full suite: build → unit → Playwright e2e | ## Release process diff --git a/docs/migration.md b/docs/migration.md index ecfbcb98..a2dff8fe 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -3,7 +3,7 @@ Mapping concepts from popular CSS frameworks to SLASHED, plus intra-project upgrade notes. -## SLASHED 0.6.25 → next +## SLASHED 0.6.25 → 0.6.26 ### `base` relative shade aliases removed diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..6ffd799d --- /dev/null +++ b/tests/README.md @@ -0,0 +1,28 @@ +# Test conventions + +Two runners, split by whether a test needs a real browser: + +- **`*.spec.js`** — Playwright (`npm test`). Runs across chromium/firefox/webkit. + Use for anything that renders CSS and reads computed styles, layout geometry, + or visual output — imports `test`/`expect` from `@playwright/test`. +- **`*.test.js`** — Node's built-in test runner (`npm run test:unit`), single + process, no browser. Use for pure fs/regex/data-shape checks that never touch + a DOM — imports `test`/`describe` from `node:test` and `assert` from + `node:assert/strict`. Wire new files into `pretest`/`test:unit` in + `package.json`. + +If a test doesn't call `page.*` or read a computed style, it almost certainly +belongs in `*.test.js`, not `*.spec.js` — see SL-027 (`coverage.test.js` used +to be Playwright-discovered despite never touching a browser, tripling its CI +cost for no reason). + +## `tier1-p*` numbering (SL-032) + +`tier1-p2-coverage`, `tier1-p7-oldengine`, `tier1-p8-modern`, and +`tier1-p10-contrast` number their properties P2/P7/P8/P10 with no P1, +P3-P6, or P9 anywhere in the repo. These numbers come from the +tier-1-color-fallback feature's original property enumeration (see each +file's own `Property N:` header comment) — only a subset of the enumerated +properties warranted a dedicated automated test; the rest were covered by +other existing tests, manual review, or judged unnecessary to automate. The +gaps are not dead history — don't renumber these files sequentially. diff --git a/tests/a11y-patterns.spec.js b/tests/a11y-patterns.spec.js index ecaba27e..2c30d363 100644 --- a/tests/a11y-patterns.spec.js +++ b/tests/a11y-patterns.spec.js @@ -4,16 +4,11 @@ // .sf-focus-parent, .sf-clickable-parent, // touch-target token, forced-colors, disabled cursor. import { test, expect } from '@playwright/test'; -import path from 'node:path'; - -const BUNDLE = path.join(process.cwd(), 'badges', 'slashed.optimal.css'); +import { renderWithBundle, NO_TRANSITIONS_STYLE } from './render-helpers.js'; async function setup(page, html) { - await page.setViewportSize({ width: 800, height: 600 }); - await page.setContent(`${html}`); - await page.addStyleTag({ path: BUNDLE }); // Disable transitions so computed property reads are stable (no mid-animation values). - await page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; animation-duration: 0s !important; }' }); + await renderWithBundle(page, html, { width: 800, height: 600, extraStyle: NO_TRANSITIONS_STYLE }); } // ── .sr-only ──────────────────────────────────────────────────── diff --git a/tests/container-queries.spec.js b/tests/container-queries.spec.js index c7e50853..e6236de3 100644 --- a/tests/container-queries.spec.js +++ b/tests/container-queries.spec.js @@ -5,9 +5,7 @@ // setupInContainer pins font-size to 16px so em-based CQ thresholds are stable // across browsers (WebKit 26+ resolves em against inherited font-size, not 16px). import { test, expect } from '@playwright/test'; -import path from 'node:path'; - -const BUNDLE = path.join(process.cwd(), 'badges', 'slashed.optimal.css'); +import { BUNDLE } from './render-helpers.js'; // Wraps grid HTML in a container of a known inline-size so anonymous // @container queries fire against that width, not the viewport. diff --git a/tests/layers.spec.js b/tests/layers.spec.js index 96989c17..6d84e33e 100644 --- a/tests/layers.spec.js +++ b/tests/layers.spec.js @@ -1,6 +1,6 @@ // @ts-check import { test, expect } from '@playwright/test'; -import path from 'node:path'; +import { BUNDLE } from './render-helpers.js'; /** * Layer ordering invariant test. @@ -26,7 +26,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const color = await page.locator('.layer-check').evaluate(el => getComputedStyle(el).color @@ -42,7 +42,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const opacity = await page.locator('.layer-check-2').evaluate(el => getComputedStyle(el).opacity @@ -58,7 +58,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const opacity = await page.locator('.layer-check-3').evaluate(el => getComputedStyle(el).opacity @@ -76,7 +76,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const color = await page.locator('.layer-check-4').evaluate(el => getComputedStyle(el).color @@ -94,7 +94,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const opacity = await page.locator('.layer-check-5').evaluate(el => getComputedStyle(el).opacity @@ -113,7 +113,7 @@ test.describe('Layer ordering', () => {
test
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const opacity = await page.locator('.layer-check-6').evaluate(el => getComputedStyle(el).opacity diff --git a/tests/layout.spec.js b/tests/layout.spec.js index 5650571a..3f14087b 100644 --- a/tests/layout.spec.js +++ b/tests/layout.spec.js @@ -1,14 +1,10 @@ // @ts-check // Behavioural tests for core/layout.css — every layout primitive. import { test, expect } from '@playwright/test'; -import path from 'node:path'; - -const BUNDLE = path.join(process.cwd(), 'badges', 'slashed.optimal.css'); +import { renderWithBundle } from './render-helpers.js'; async function setup(page, html) { - await page.setViewportSize({ width: 1200, height: 900 }); - await page.setContent(`${html}`); - await page.addStyleTag({ path: BUNDLE }); + await renderWithBundle(page, html, { width: 1200, height: 900 }); } // ── .sf-section ──────────────────────────────────────────────── diff --git a/tests/macros.spec.js b/tests/macros.spec.js index 9e7f98a0..b3dcce06 100644 --- a/tests/macros.spec.js +++ b/tests/macros.spec.js @@ -3,14 +3,12 @@ // Each macro is exercised against a synthetic fixture and its // observable effect verified via getComputedStyle / layout reads. import { test, expect } from '@playwright/test'; -import path from 'node:path'; - -const BUNDLE = path.join(process.cwd(), 'badges', 'slashed.optimal.css'); +import { renderWithBundle } from './render-helpers.js'; async function setup(page, html) { - await page.setViewportSize({ width: 800, height: 600 }); - await page.setContent(`${html}`); - await page.addStyleTag({ path: BUNDLE }); + // Unlike the other spec files' setup(), body has no margin:0 here — + // preserved as-is (bodyMargin: null omits the style attribute entirely). + await renderWithBundle(page, html, { width: 800, height: 600, bodyMargin: null }); } test.describe('macro: .sf-flow', () => { diff --git a/tests/print.spec.js b/tests/print.spec.js index 04c2b782..11676bcc 100644 --- a/tests/print.spec.js +++ b/tests/print.spec.js @@ -1,6 +1,6 @@ // @ts-check import { test, expect } from '@playwright/test'; -import path from 'node:path'; +import { BUNDLE } from './render-helpers.js'; /** * Print color preservation smoke test. @@ -12,7 +12,7 @@ test.describe('Print styles', () => { test(' retains non-transparent background in print', async ({ page }) => { await page.emulateMedia({ media: 'print' }); await page.setContent(`

Highlighted text

`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); const bg = await page.locator('mark').evaluate(el => getComputedStyle(el).backgroundColor @@ -29,7 +29,7 @@ test.describe('Print styles', () => {
Should be hidden
Should be visible
`); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); await page.emulateMedia({ media: 'print' }); const hiddenDisplay = await page.locator('#hidden-el').evaluate(el => @@ -47,7 +47,7 @@ test.describe('Print styles', () => { await page.setContent(` `); - await page.addStyleTag({ path: path.join(process.cwd(), 'badges', 'slashed.optimal.css') }); + await page.addStyleTag({ path: BUNDLE }); await page.emulateMedia({ media: 'print' }); const pca = await page.locator('.print-color-exact').evaluate(el => diff --git a/tests/render-helpers.js b/tests/render-helpers.js new file mode 100644 index 00000000..af5b9234 --- /dev/null +++ b/tests/render-helpers.js @@ -0,0 +1,40 @@ +// Shared Playwright test-fixture helpers (SL-033). The optimal bundle path +// and the viewport/doctype/addStyleTag boilerplate were duplicated across +// a11y-patterns.spec.js, layout.spec.js, macros.spec.js, states-full.spec.js, +// and typography.spec.js with near-identical setup() functions. The three +// real differences between them (viewport size, whether gets +// style="margin:0", and whether transitions are disabled) are now the +// options renderWithBundle() takes — see each call site for its values. +// +// container-queries.spec.js's setupInContainer() wraps content in a sized +// container div and pins font-size for @container em thresholds; that shape +// is genuinely different from the flat case here, so it keeps its own local +// function, but imports BUNDLE from here instead of redefining it. +// +// layers.spec.js and print.spec.js inline page.setContent per-test (each +// test stages different competing rules in its own