From 84a649c4bb6387b3fcaa5a6852e0accd2ae5844a Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 1 Oct 2026 06:43:00 +0200 Subject: [PATCH] fix(ui): test the token guard's file selection, drop duplicate dark elevation The guard's tests only fed source strings to the matcher, so a pathspec change that skipped nested files would have kept CI green. Export the file selection and test it against a temporary repository: nested app and lib sources are scanned, specs, untracked files and paths outside apps/libs are not. The dark theme context re-declared the elevation tokens with the same black shadows html already declares; keep only the color roles there (756 bytes). Co-Authored-By: Claude Opus 5.5 --- apps/web/src/m3-theme.scss | 3 +- tools/nx/check-material-token-overrides.mjs | 21 +++++---- .../check-material-token-overrides.test.mjs | 46 ++++++++++++++++++- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/apps/web/src/m3-theme.scss b/apps/web/src/m3-theme.scss index 6acf2e08e..90cf70856 100644 --- a/apps/web/src/m3-theme.scss +++ b/apps/web/src/m3-theme.scss @@ -141,8 +141,9 @@ html { .dark-theme { @include mat.all-component-colors($dark-theme); + // Only the color roles differ per theme: the elevation shadows are + // the same black in both, so html's declarations already apply here. @include mat.system-level-colors($dark-theme); - @include mat.system-level-elevation($dark-theme); // ── Dark graphite theme ──────────────────────────────────────────── // Inspired by both Slack (sidebar distinction) and GitButler (data density) diff --git a/tools/nx/check-material-token-overrides.mjs b/tools/nx/check-material-token-overrides.mjs index ed79a6809..8c21a3ffb 100644 --- a/tools/nx/check-material-token-overrides.mjs +++ b/tools/nx/check-material-token-overrides.mjs @@ -40,16 +40,11 @@ export function findDeadMaterialTokens(file, source) { return findings; } -const isMain = - process.argv[1] && - path.resolve(process.argv[1]) === - path.resolve(fileURLToPath(import.meta.url)); - -if (isMain) { - const rootDir = process.cwd(); +/** Tracked files under `rootDir` that the guard reads, at any depth. */ +export function listScannedFiles(rootDir) { // No shell: `cmd.exe` treats single quotes as literal characters, so a // POSIX-quoted pathspec reaches git intact on Windows and matches nothing. - const files = execFileSync('git', ['ls-files', ...SCANNED_PATHSPECS], { + return execFileSync('git', ['ls-files', ...SCANNED_PATHSPECS], { cwd: rootDir, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, @@ -58,6 +53,16 @@ if (isMain) { .split('\n') .filter(Boolean) .filter(isScanned); +} + +const isMain = + process.argv[1] && + path.resolve(process.argv[1]) === + path.resolve(fileURLToPath(import.meta.url)); + +if (isMain) { + const rootDir = process.cwd(); + const files = listScannedFiles(rootDir); const findings = []; for (const file of files) { diff --git a/tools/nx/check-material-token-overrides.test.mjs b/tools/nx/check-material-token-overrides.test.mjs index e37077365..7025fb060 100644 --- a/tools/nx/check-material-token-overrides.test.mjs +++ b/tools/nx/check-material-token-overrides.test.mjs @@ -1,7 +1,14 @@ import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; import { test } from 'node:test'; -import { findDeadMaterialTokens } from './check-material-token-overrides.mjs'; +import { + findDeadMaterialTokens, + listScannedFiles, +} from './check-material-token-overrides.mjs'; test('reports retired --mdc-* declarations and reads with their location', () => { const source = [ @@ -32,3 +39,40 @@ test('accepts current --mat-* tokens and override mixins', () => { assert.deepEqual(findDeadMaterialTokens('b.scss', source), []); }); + +test('selects tracked app and lib sources at any depth, but not specs', async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), 'mat-token-guard-')); + const git = (...args) => + execFileSync('git', args, { cwd: rootDir, stdio: 'pipe' }); + try { + git('init', '-q'); + const files = { + 'libs/ui/feature/src/lib/deep/panel.component.scss': '', + 'libs/ui/feature/src/lib/deep/panel.component.html': '', + 'libs/ui/feature/src/lib/deep/panel.component.ts': '', + 'libs/ui/feature/src/lib/deep/panel.component.spec.ts': '', + 'apps/web/src/styles.scss': '', + 'apps/web/src/vendor.css': '', + 'tools/outside.scss': '', + }; + for (const [file, content] of Object.entries(files)) { + await mkdir(path.dirname(path.join(rootDir, file)), { + recursive: true, + }); + await writeFile(path.join(rootDir, file), content); + } + git('add', '.'); + // Untracked files are not part of the checkout CI sees. + await writeFile(path.join(rootDir, 'apps/web/src/untracked.scss'), ''); + + assert.deepEqual(listScannedFiles(rootDir).sort(), [ + 'apps/web/src/styles.scss', + 'apps/web/src/vendor.css', + 'libs/ui/feature/src/lib/deep/panel.component.html', + 'libs/ui/feature/src/lib/deep/panel.component.scss', + 'libs/ui/feature/src/lib/deep/panel.component.ts', + ]); + } finally { + await rm(rootDir, { recursive: true, force: true }); + } +});