From 1742fadafb2265fa2b7acb8db23381f40a3a5e97 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 26 Jul 2026 09:34:22 +1000 Subject: [PATCH] Make the checks and tests mean something (#270) Delete the theme tests that could not fail. validateContrast already hard-gates text against every surface at 4.5:1 and border.control against canvas and recessed at 3:1, throwing before any assertion recomputing those ratios from the emitted CSS could run. Replace each with the honest statement of the same property: that building the theme does not throw. The neutral half of the dark subtle-hover test was dead for the same reason, so keep only the accent pair, which is genuinely ungated. Drop the loading-skeleton tautology, which asserted a contrast ratio above 1 immediately after asserting the two colours differ. The real gate is in loading-skeleton.browser.test.ts, samples the animated pulse and requires 1.4:1, so it both subsumes and beats the static check. Drop the migration-era assertions: the hand-written list of 25 removed tokens, and the hardcoded 128 leaf count, which contract.test.ts already asserts properly. Derive the count from the contract instead. Drop the theme test that read the combobox stylesheet source as text and grepped it for a string. Make the local and CI checks agree. `pnpm run check` ran check:barrels but not root formatting; CI ran root formatting but not check:barrels, so a barrel violation could merge and a docs-only change could pass locally and fail CI. Adding //#check:format-root to the check task and having CI run that same task leaves both sides on an identical 28-task graph, with --affected only ever an additive PR filter. knip stays a separate unscoped invocation because it is repo-wide. Run the docs app tests in CI, which nothing did. Four of them failed on main because the harness rendered ThemeControls as a sibling of the StoryWrapper that owns the only IconSpritesheetProvider; wrapping DocsThemeRoot mirrors what __root.tsx actually does. Ignore the docs failure screenshots, which were written into a tracked path. Give the token board the coverage check its own comment claims. The swatch count and the tree builder now both derive from the contract. Verified by dropping a leaf: 3 unit tests fail and the story fails with "expected 46 to be 47". --- .github/workflows/check.yml | 6 +- .github/workflows/storybook.yml | 35 +++++++ apps/docs/.gitignore | 1 + .../theme-controls.browser.test.tsx | 3 + .../react/src/theme/build-theme.test.ts | 90 +++++++----------- .../src/theme/color-token-board.stories.tsx | 8 +- .../react/src/theme/color-token-board.test.ts | 46 +++++++++ .../react/src/theme/color-token-board.tsx | 9 +- .../react/src/theme/define-theme.test.ts | 93 ++++--------------- turbo.json | 8 +- 10 files changed, 158 insertions(+), 141 deletions(-) create mode 100644 packages/@luke-ui/react/src/theme/color-token-board.test.ts diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 54483adc..fa3dbf7e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -45,7 +45,11 @@ jobs: TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} TURBO_SCM_HEAD: ${{ github.event.pull_request.head.sha }} run: | - corepack pnpm exec turbo run check:format-root check:format check:lint check:types ${{ github.event_name == 'pull_request' && '--affected' || '' }} + # `check` is the same turbo task the root `pnpm run check` script runs (`turbo run check + # check:unused`), so CI and local checks are the same task graph — barrels, root format, + # package format, lint, and types. `--affected` is added here only as a PR speed + # optimisation and is never applied to `check:unused`. + corepack pnpm exec turbo run check ${{ github.event_name == 'pull_request' && '--affected' || '' }} # knip is repo-wide, but --affected only selects packages whose own files # changed, so it must run unscoped. Running it via turbo (rather than the # bare `knip` script) pulls in its generate dependencies first. diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index f8500b19..eab9a4ad 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -49,3 +49,38 @@ jobs: corepack pnpm --filter @luke-ui/react run test:unit && corepack pnpm --filter @luke-ui/react run test:browser && corepack pnpm --filter @luke-ui/react run test:storybook + + docs-tests: + name: docs-tests + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup tooling with mise + uses: jdx/mise-action@v4 + with: + install: true + cache: true + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: corepack pnpm install --frozen-lockfile + + - name: Install Playwright Chromium + run: corepack pnpm --filter docs exec playwright install --with-deps chromium + + - name: Build package + run: corepack pnpm build:packages + + - name: Generate docs app files + run: corepack pnpm --filter docs run generate + + - name: Run docs tests + run: corepack pnpm --filter docs run test diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 6c8ca7df..1a992b5c 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -15,3 +15,4 @@ /server/build /test-results/ node_modules +src/**/__screenshots__/ diff --git a/apps/docs/src/components/theme-controls.browser.test.tsx b/apps/docs/src/components/theme-controls.browser.test.tsx index 520fc420..6ba3efbe 100644 --- a/apps/docs/src/components/theme-controls.browser.test.tsx +++ b/apps/docs/src/components/theme-controls.browser.test.tsx @@ -189,6 +189,9 @@ function renderTheme( defaultTheme={options.defaultTheme ?? 'light'} enableSystem={options.enableSystem ?? false} > + {/* Mirrors `__root.tsx`'s real tree shape: `IconSpritesheetProvider` wraps + `DocsThemeRoot` there too, and `ThemeControls` (rendered by these tests) consumes it + through `ColorModeToggle`'s icons. */} {children} diff --git a/packages/@luke-ui/react/src/theme/build-theme.test.ts b/packages/@luke-ui/react/src/theme/build-theme.test.ts index 5b53fc8f..52fba958 100644 --- a/packages/@luke-ui/react/src/theme/build-theme.test.ts +++ b/packages/@luke-ui/react/src/theme/build-theme.test.ts @@ -533,48 +533,28 @@ describe('compileTheme diagnostics', () => { expect(modeDiagnostics.families.accent.solidAnchor.satisfied).toBe(true); // The canvas surface equals the resolved background anchor. expect(modeDiagnostics.surfaces.canvas).toBeDefined(); - // Every recorded contrast check for a fully compiled theme's hard gates passes; advisory - // border checks may sit below their nominal 3:1 requirement. + // The diagnostics data model records every hard-gated text check, not just failures, so + // tooling (the "Theme/Diagnostics" story) can display the full matrix. Asserting `passes` on + // each would be dead: `compileTheme` above already throws `ThemeContrastError` before + // returning if any hard check failed, so every recorded hard check necessarily passed already. const hardTextChecks = modeDiagnostics.contrastChecks.filter( (check) => check.required === 4.5, ); expect(hardTextChecks.length).toBeGreaterThan(0); - for (const check of hardTextChecks) expect(check.passes).toBe(true); } }); }); describe('bundled themes meet WCAG 2.2 AA', () => { - const surfaceVarNames = [ - '--luke-color-surface-canvas', - '--luke-color-surface-recessed', - '--luke-color-surface-floating', - '--luke-color-surface-overlay', - ]; - for (const foundation of [tactileFoundation, paperFoundation]) { - it(`${foundation.name} passes recomputed text contrast in both modes`, () => { - const blocks = splitBlocks(buildTheme(foundation)); - for (const block of [blocks.baseLight, blocks.mediaDark]) { - const textPrimary = parseColor(extractValue(block, '--luke-color-text-primary')); - for (const varName of surfaceVarNames) { - const surface = parseColor(extractValue(block, varName)); - expect(contrastRatio(textPrimary, surface)).toBeGreaterThanOrEqual(4.5); - } - } - }); - - it(`${foundation.name} hard-gates border.control at >=3:1 against canvas and recessed in both modes`, () => { - const blocks = splitBlocks(buildTheme(foundation)); - for (const block of [blocks.baseLight, blocks.mediaDark]) { - const borderControl = parseColor(extractValue(block, '--luke-color-border-control')); - const canvas = parseColor(extractValue(block, '--luke-color-surface-canvas')); - const recessed = parseColor(extractValue(block, '--luke-color-surface-recessed')); - // Stage 6 Option B: border.control is a solved contrast boundary, not a scale-step alias, - // so it must clear the 3:1 non-text gate against both base surfaces. - expect(contrastRatio(borderControl, canvas)).toBeGreaterThanOrEqual(3); - expect(contrastRatio(borderControl, recessed)).toBeGreaterThanOrEqual(3); - } + // `validateContrast` in build-theme.ts already hard-gates text-vs-surface contrast (>=4.5:1, + // every surface) and border.control-vs-surface contrast (>=3:1, canvas and recessed) for every + // mode, throwing `ThemeContrastError` on any miss (exercised directly in "buildTheme contrast + // failures" below). Recomputing those exact ratios from the emitted CSS here can never fail: if + // either gate had missed, `buildTheme` would already have thrown before this assertion ran. The + // honest statement of the same property is that building the bundled theme does not throw. + it(`${foundation.name} compiles without a WCAG hard-gate failure`, () => { + expect(() => buildTheme(foundation)).not.toThrow(); }); it(`${foundation.name} keeps light canvas neutral, recessed surfaces white, and dark wells distinct`, () => { @@ -593,18 +573,20 @@ describe('bundled themes meet WCAG 2.2 AA', () => { expect(darkCanvas.l - darkRecessed.l).toBeGreaterThanOrEqual(0.02); }); - it(`${foundation.name} keeps dark subtle hover states legible for primary text`, () => { - const { mediaDark } = splitBlocks(buildTheme(foundation)); - const textPrimary = parseColor(extractValue(mediaDark, '--luke-color-text-primary')); + it(`${foundation.name} keeps dark accent subtle-hover legible for primary text`, () => { // The subtle component surfaces (scale steps 3-5) ramp from the canvas independently of the // elevation surfaces, so v2 no longer pins them apart from `floating`; what still matters is - // that primary text stays legible on the hovered subtle surface. - for (const intent of ['neutral', 'accent']) { - const subtleHover = parseColor( - extractValue(mediaDark, `--luke-color-intent-${intent}-surface-subtle-hover`), - ); - expect(contrastRatio(textPrimary, subtleHover)).toBeGreaterThanOrEqual(4.5); - } + // that primary text stays legible on the hovered subtle surface. Neutral subtleHover is + // excluded here: `validateContrast` already hard-gates `color.text.primary` against every + // neutral surface state (including subtleHover) at >=4.5:1, so recomputing that exact pair + // would be dead — accent subtleHover is not one of the hard-gated pairs, so it is the one + // worth recomputing. + const { mediaDark } = splitBlocks(buildTheme(foundation)); + const textPrimary = parseColor(extractValue(mediaDark, '--luke-color-text-primary')); + const subtleHover = parseColor( + extractValue(mediaDark, '--luke-color-intent-accent-surface-subtle-hover'), + ); + expect(contrastRatio(textPrimary, subtleHover)).toBeGreaterThanOrEqual(4.5); }); it(`${foundation.name} generates subtle, distinct intent borders`, () => { @@ -613,8 +595,8 @@ describe('bundled themes meet WCAG 2.2 AA', () => { const surfaces = ['canvas', 'recessed'].map((surface) => { return parseColor(extractValue(block, `--luke-color-surface-${surface}`)); }); - // border.control is excluded here: it is now a solved contrast boundary (>=3:1, asserted - // separately above), not one of these subtle Radix-style separators. + // border.control is excluded here: it is now a solved contrast boundary, hard-gated at + // >=3:1 by `validateContrast`, not one of these subtle Radix-style separators. const borderVarNames = ['accent', 'info', 'success', 'warning', 'danger'].map( (intent) => `--luke-color-intent-${intent}-border`, ); @@ -635,21 +617,11 @@ describe('bundled themes meet WCAG 2.2 AA', () => { } }); -describe('bundled loading skeleton surfaces', () => { - for (const foundation of [tactileFoundation, paperFoundation]) { - it(`${foundation.name} keeps loading skeletons distinct from the canvas in both modes`, () => { - const blocks = splitBlocks(buildTheme(foundation)); - for (const block of [blocks.baseLight, blocks.mediaDark]) { - const canvas = parseColor(extractValue(block, '--luke-color-surface-canvas')); - const skeleton = parseColor(extractValue(block, '--luke-color-loading-skeleton')); - // v2 aliases the loading skeleton onto the neutral scale's step 7 (a visible placeholder - // tint, clearing the pulse-extreme contrast gate the component's browser test enforces). - expect(skeleton).not.toEqual(canvas); - expect(contrastRatio(skeleton, canvas)).toBeGreaterThan(1); - } - }); - } -}); +// The loading-skeleton contrast gate lives in `loading-skeleton.browser.test.ts` ("keeps the +// pulse's dimmest frame perceptible against every bundled theme's canvas"), which samples the real +// animated pulse and requires >=1.4:1 at both its brightest and dimmest frame. That subsumes and is +// stricter than a static "skeleton !== canvas and contrastRatio > 1" check would ever be — the +// latter is a tautology once the two colours are already known to differ, so it added no coverage. describe('bundled theme identity', () => { it('exports class-name constants that match the emitted identity classes', () => { diff --git a/packages/@luke-ui/react/src/theme/color-token-board.stories.tsx b/packages/@luke-ui/react/src/theme/color-token-board.stories.tsx index 139d6049..7af28341 100644 --- a/packages/@luke-ui/react/src/theme/color-token-board.stories.tsx +++ b/packages/@luke-ui/react/src/theme/color-token-board.stories.tsx @@ -1,6 +1,7 @@ import { expect } from 'storybook/test'; import preview from '../../.storybook/preview.js'; import { ColorTokenBoard } from './color-token-board.js'; +import { flattenThemeContract } from './contract.js'; const meta = preview.meta({ component: ColorTokenBoard, @@ -8,6 +9,8 @@ const meta = preview.meta({ title: 'Theme/Color token board', }); +const colorLeafCount = flattenThemeContract().filter(([path]) => path.startsWith('color.')).length; + /** * Every `color.*` semantic contract leaf, resolved for the active theme and colour mode. Switch the * theme and colour mode in the Storybook toolbar to compare. Captured in full by @@ -17,7 +20,10 @@ const meta = preview.meta({ */ export const Board = meta.story({ play: async ({ canvasElement }) => { + // Derived from the contract, not hardcoded, so the board's own claim that coverage cannot + // drift out of sync with the contract is actually enforced: this fails the moment a rendered + // swatch count and the contract's colour-leaf count disagree. const swatches = canvasElement.querySelectorAll('[role="img"]'); - await expect(swatches.length).toBeGreaterThan(0); + await expect(swatches.length).toBe(colorLeafCount); }, }); diff --git a/packages/@luke-ui/react/src/theme/color-token-board.test.ts b/packages/@luke-ui/react/src/theme/color-token-board.test.ts new file mode 100644 index 00000000..7bf05d84 --- /dev/null +++ b/packages/@luke-ui/react/src/theme/color-token-board.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vite-plus/test'; +import type { ColorTreeNode } from './color-token-board.js'; +import { buildColorTree } from './color-token-board.js'; +import { flattenThemeContract } from './contract.js'; + +function collectLeaves(node: ColorTreeNode): Array<{ path: string; varName: string }> { + if (node.kind === 'leaf') return [{ path: node.path, varName: node.varName }]; + return Object.values(node.children).flatMap(collectLeaves); +} + +describe('buildColorTree', () => { + const colorPairs = flattenThemeContract().filter(([path]) => path.startsWith('color.')); + + it('produces exactly one leaf per color.* contract path, derived from the contract', () => { + const leaves = collectLeaves(buildColorTree()); + expect(leaves).toHaveLength(colorPairs.length); + }); + + it('carries every contract path and var name through unchanged', () => { + const byPath = (a: [string, string], b: [string, string]) => a[0].localeCompare(b[0]); + const leaves = collectLeaves(buildColorTree()); + const leavesByPath = new Map(leaves.map((leaf) => [leaf.path, leaf.varName])); + expect([...leavesByPath.entries()].sort(byPath)).toEqual([...colorPairs].sort(byPath)); + }); + + it('nests a top-level colour leaf directly, with no intermediate group', () => { + const scrim = buildColorTree().children.scrim; + expect(scrim).toEqual({ kind: 'leaf', path: 'color.scrim', varName: '--luke-color-scrim' }); + }); + + it('nests a deep intent leaf under its full chain of contract path segments', () => { + const tree = buildColorTree(); + const intent = tree.children.intent; + if (intent?.kind !== 'group') throw new Error('expected an intent group'); + const danger = intent.children.danger; + if (danger?.kind !== 'group') throw new Error('expected a danger group'); + const surface = danger.children.surface; + if (surface?.kind !== 'group') throw new Error('expected a surface group'); + + expect(surface.children.subtle).toEqual({ + kind: 'leaf', + path: 'color.intent.danger.surface.subtle', + varName: '--luke-color-intent-danger-surface-subtle', + }); + }); +}); diff --git a/packages/@luke-ui/react/src/theme/color-token-board.tsx b/packages/@luke-ui/react/src/theme/color-token-board.tsx index b60bf7e8..2821d957 100644 --- a/packages/@luke-ui/react/src/theme/color-token-board.tsx +++ b/packages/@luke-ui/react/src/theme/color-token-board.tsx @@ -18,12 +18,12 @@ interface ColorLeafNode { varName: string; } -interface ColorGroupNode { +export interface ColorGroupNode { kind: 'group'; children: Record; } -type ColorTreeNode = ColorLeafNode | ColorGroupNode; +export type ColorTreeNode = ColorLeafNode | ColorGroupNode; // Capped at the contract's deepest colour group (color.intent..surface.); depths past // this reuse the smallest heading. @@ -167,9 +167,10 @@ function ColorSwatch({ label, path, varName }: { label: string; path: string; va /** * Builds the colour subtree from `flattenThemeContract()`'s flat `[path, varName]` pairs, grouped by - * path segment so the board's structure mirrors `contract.ts`'s own nesting exactly. + * path segment so the board's structure mirrors `contract.ts`'s own nesting exactly. Exported for + * `color-token-board.test.ts`, which unit-tests the grouping logic directly. */ -function buildColorTree(): ColorGroupNode { +export function buildColorTree(): ColorGroupNode { const root: ColorGroupNode = { children: {}, kind: 'group' }; for (const [path, varName] of flattenThemeContract()) { if (!path.startsWith('color.')) continue; diff --git a/packages/@luke-ui/react/src/theme/define-theme.test.ts b/packages/@luke-ui/react/src/theme/define-theme.test.ts index 517d829d..8bba30e2 100644 --- a/packages/@luke-ui/react/src/theme/define-theme.test.ts +++ b/packages/@luke-ui/react/src/theme/define-theme.test.ts @@ -1,6 +1,5 @@ -import { readFile } from 'node:fs/promises'; import { describe, expect, it } from 'vite-plus/test'; -import { contrastRatio, gamutMapOklch, parseColor } from './color.js'; +import { gamutMapOklch, parseColor } from './color.js'; import { flattenThemeContract } from './contract.js'; import { defaultDepth, defineTheme, normalizeTheme } from './define-theme.js'; import { paperTheme, tactileTheme } from './foundations.js'; @@ -32,34 +31,19 @@ function extractValue(block: string, varName: string): string { } const ACCENT_SOLID = '--luke-color-intent-accent-surface-solid'; -const SURFACE_VAR_NAMES = [ - '--luke-color-surface-canvas', - '--luke-color-surface-recessed', - '--luke-color-surface-floating', - '--luke-color-surface-overlay', -]; describe('defineTheme colour-only authoring', () => { - it('builds a theme from just an accent and a neutral character, accessible in both modes', () => { - const css = defineTheme({ - color: { accent: '#3b82f6', neutralStyle: 'cool' }, - name: 'colour-only', - }); - const blocks = splitBlocks(css); - for (const block of [blocks.baseLight, blocks.mediaDark]) { - const textPrimary = parseColor(extractValue(block, '--luke-color-text-primary')); - const borderControl = parseColor(extractValue(block, '--luke-color-border-control')); - const canvas = parseColor(extractValue(block, '--luke-color-surface-canvas')); - const recessed = parseColor(extractValue(block, '--luke-color-surface-recessed')); - for (const varName of SURFACE_VAR_NAMES) { - const surface = parseColor(extractValue(block, varName)); - expect(contrastRatio(textPrimary, surface)).toBeGreaterThanOrEqual(4.5); - } - // border.control is a solved contrast boundary (Stage 6 Option B), not a scale-step alias, so - // it must clear the 3:1 non-text gate against both base surfaces. - expect(contrastRatio(borderControl, canvas)).toBeGreaterThanOrEqual(3); - expect(contrastRatio(borderControl, recessed)).toBeGreaterThanOrEqual(3); - } + // `defineTheme` compiles through `buildTheme`, whose `validateContrast` already hard-gates + // text-vs-surface contrast (>=4.5:1, every surface) and border.control-vs-surface contrast + // (>=3:1, canvas and recessed) for every mode, throwing `ThemeContrastError` on any miss. + // Recomputing those exact ratios from the emitted CSS here can never fail: if either gate had + // missed, `defineTheme` would already have thrown before an assertion could run. The honest + // statement of the same property is that building from just an accent and a neutral character + // does not throw. + it('builds a theme from just an accent and a neutral character without a WCAG hard-gate failure', () => { + expect(() => + defineTheme({ color: { accent: '#3b82f6', neutralStyle: 'cool' }, name: 'colour-only' }), + ).not.toThrow(); }); }); @@ -249,44 +233,12 @@ describe('normalizeTheme resolves the source-tier `background` split from `neutr }); }); -/** The 25 leaves the 128-leaf flip removes, by stable `--luke-*` variable name. */ -const REMOVED_VAR_NAMES = [ - '--luke-color-surface-resting', - '--luke-color-surface-disabled', - '--luke-color-border-disabled', - ...['info', 'success', 'warning'].flatMap((intent) => - [ - 'surface-subtle-hover', - 'surface-subtle-pressed', - 'surface-solid', - 'surface-solid-hover', - 'surface-solid-pressed', - 'on-solid', - ].map((leaf) => `--luke-color-intent-${intent}-${leaf}`), - ), - '--luke-motion-duration-medium', - '--luke-motion-duration-slow', - '--luke-motion-duration-ambient', - '--luke-motion-easing-enter', -]; - /** Extracts the set of unique `--luke-*` variable names declared in a stylesheet. */ function emittedVarNames(css: string): Set { return new Set([...css.matchAll(/(--luke-[a-z0-9-]+):/g)].map((match) => match[1] ?? '')); } -describe('the reduced 128-leaf contract', () => { - it('flattens to exactly 128 leaves with scrim added and the 25 removed leaves absent', () => { - const names = flattenThemeContract().map(([, varName]) => varName); - expect(names).toHaveLength(128); - expect(names).toContain('--luke-color-scrim'); - // text.disabled kept its stable CSS variable across the rename from color.textDisabled. - expect(names).toContain('--luke-color-text-disabled'); - for (const removed of REMOVED_VAR_NAMES) expect(names).not.toContain(removed); - }); -}); - -describe('defineTheme emits the reduced contract for the bundled themes', () => { +describe('defineTheme emits the full contract for the bundled themes', () => { const contractNames = flattenThemeContract().map(([, varName]) => varName); for (const [name, input] of [ @@ -296,17 +248,16 @@ describe('defineTheme emits the reduced contract for the bundled themes', () => const css = defineTheme(input); const emitted = emittedVarNames(css); - it(`${name} emits exactly the 128 contract variables, including scrim and disabled text`, () => { - expect(emitted.size).toBe(128); + it(`${name} emits exactly the contract variables, including scrim and disabled text`, () => { + // Derived from the contract, not hardcoded: `contract.test.ts` already asserts the typed + // `vars` tree has exactly as many leaves as `flattenThemeContract()`, so this only needs to + // check that a bundled theme's emitted CSS matches that same list, not restate its length. + expect(emitted.size).toBe(contractNames.length); expect([...emitted].sort()).toEqual([...contractNames].sort()); expect(emitted.has('--luke-color-scrim')).toBe(true); expect(emitted.has('--luke-color-text-disabled')).toBe(true); }); - it(`${name} drops every one of the 25 removed leaves`, () => { - for (const removed of REMOVED_VAR_NAMES) expect(emitted.has(removed)).toBe(false); - }); - it(`${name} keeps feedback intents static — soft kit only, no solid or state variables`, () => { for (const intent of ['info', 'success', 'warning']) { expect(css).not.toContain(`--luke-color-intent-${intent}-surface-solid`); @@ -340,11 +291,3 @@ describe('defineTheme emits the reduced contract for the bundled themes', () => expect(css).toContain('--luke-color-scrim: oklch(0 0 0 / 0.4);'); }); }); - -describe('the combobox tray scrim adopts the scrim token', () => { - it('replaces the hardcoded rgb(0 0 0 / 20%) literal with vars.color.scrim', async () => { - const source = await readFile(new URL('../recipes/combobox.css.ts', import.meta.url), 'utf8'); - expect(source).not.toContain('rgb(0 0 0 / 20%)'); - expect(source).toContain('vars.color.scrim'); - }); -}); diff --git a/turbo.json b/turbo.json index 88665b72..18226c47 100644 --- a/turbo.json +++ b/turbo.json @@ -36,7 +36,13 @@ "outputs": ["storybook-static/**"] }, "check": { - "dependsOn": ["check:barrels", "check:format", "check:lint", "check:types"] + "dependsOn": [ + "check:barrels", + "check:format", + "//#check:format-root", + "check:lint", + "check:types" + ] }, "check:barrels": { "dependsOn": ["generate"] -- 2.51.2