Test check (iii) and fix two bugs it exposed in the application-CSS scan

Plan step 41(iii): fixtures/tests for every literal kind (colour, radius,
shadow, font size/weight, line height, letter spacing, easing, duration) and
an off-scale media query, plus material-scheme.css excluded by name and by
its generated header wherever it sits among the scanned paths. Also adds a
fixture proving Tailwind-shaped bare words in running prose are never read
as classes.

Two fixes found by writing the fixtures rather than only the implementation:
a directly-scanned .css file was also running through the Blade/PHP/JS checks
(the same file's `ease-in-out` in a `transition` value tripped the old scale
check a second time), so a CSS file is now check (iii)'s alone; and
`withoutTokenFunctions()` leaves a `var()`/`calc()` call's own name and
parentheses standing once its arguments are blanked, which
`isSafeLiteralValue()` did not yet recognise as the same "nothing to see
here" as an empty value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 08:16:15 +02:00
co-authored by Claude Sonnet 5
parent b998683762
commit 09f4edd31d
6 changed files with 84 additions and 2 deletions
+32
View File
@@ -411,6 +411,38 @@ it('reports a package tag the application shadows with its own component class',
]);
});
it('finds every literal design value and off-scale media query in the application\'s own CSS', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/app-css/app.css'))->violations());
expect($violations)->toBe([
'app-css/app.css:3 literal colour `#ff0000` — use `var(--md-sys-color-*)`',
'app-css/app.css:4 literal colour `rgb(0, 0, 0)` — use `var(--md-sys-color-*)`',
'app-css/app.css:6 literal colour `rgba(0, 0, 0, 0.2)` — use `var(--md-sys-color-*)`',
'app-css/app.css:5 literal radius `border-radius: 12px` — use `var(--md-sys-shape-corner-*)`',
'app-css/app.css:6 literal shadow `box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)` — use `var(--md-sys-elevation-*)`',
'app-css/app.css:7 literal font size `font-size: 14px` — use `var(--md-sys-typescale-*)`, which sets size, line height and weight together',
'app-css/app.css:8 literal font weight `font-weight: 600` — use `var(--md-sys-typescale-*)` or `var(--md-sys-typescale-emphasized-*)`',
'app-css/app.css:9 literal line height `line-height: 1.4` — use `var(--md-sys-typescale-*)`, which sets it with the size',
'app-css/app.css:10 literal letter spacing `letter-spacing: 0.02em` — use `var(--md-sys-typescale-*-tracking)`',
'app-css/app.css:11 literal easing in `transition: opacity 200ms ease-in-out` — use `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)`, paired with its `-duration`',
'app-css/app.css:11 literal duration in `transition: opacity 200ms ease-in-out` — use `var(--md-sys-motion-…-duration)`, paired with its easing',
"app-css/app.css:21 media query width `700px` is not one of M3's breakpoints — use 600, 840, 1200 or 1600px (medium, expanded, large, extra-large)",
]);
});
it('ignores the generated material-scheme.css, by name and by its generated header, wherever it sits among the scanned paths', function () {
$withScheme = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/app-css'))->violations());
$withoutScheme = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/app-css/app.css'))->violations());
expect($withScheme)->toBe($withoutScheme);
});
it('never reads a Tailwind-shaped word out of running text', function () {
$violations = DesignGuard::scan(realpath(GUARD_FIXTURES.'/running-text.blade.php'))->violations();
expect($violations)->toBe([]);
});
it('passes the package\'s own views', function () {
$violations = DesignGuard::scan([__DIR__.'/../../resources/views', __DIR__.'/../../resources/js', __DIR__.'/../../src'])->violations();