Rewrite the design guard's family table for a Tailwind-free application

Plan step 41(i): every Tailwind utility or variant in a Tailwind-free
application's views now gets a 2.0.0 hint instead of the old Tailwind-utility
replacement (which no longer compiles either) -- a layout component and prop,
an md-* class, or a var(--md-sys-*) token. New families cover flex/grid/gap,
spacing, sizing, display, text-layout and the M3 role utilities
(text-on-surface-variant, border-outline-variant, bg-surface-*, bg-primary);
the breakpoint and scale checks fold in with their hints rewritten the same
way. A class the application's own stylesheets declare is exempt.

literalClasses() now returns each class token unstripped of its variant
prefix (`sm:`, `hover:`) so the new checks can see it; the daisyUI check
strips it itself via the new withoutVariantPrefix() helper, as it always did
internally before.

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:08:08 +02:00
co-authored by Claude Sonnet 5
parent 7ec6960731
commit 08f2ae9d76
14 changed files with 984 additions and 109 deletions
+180 -68
View File
@@ -19,7 +19,9 @@ it('finds what compiles to nothing', function () {
expect(fixtureRelative($violations))->toBe([
'app/Status.php:14 colour the theme does not declare `text-red-500`',
'views/page.blade.php:3 daisyUI class `card`',
"views/page.blade.php:3 Tailwind spacing utility `p-4` compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS",
'views/page.blade.php:6 daisyUI class `btn-primary`',
'views/page.blade.php:9 Tailwind sizing utility `size-4` compiles to nothing — M3 keeps no size scale; write the literal length in your own CSS, or use `<x-pane width>`/`<x-grid min-item>` where it fits',
'views/page.blade.php:4 unknown Material Symbol `o-home`',
'views/page.blade.php:5 unknown Material Symbol `not_a_symbol`',
'views/page.blade.php:9 Blade directive `@class` inside a component tag, where it does not compile',
@@ -29,20 +31,30 @@ it('finds what compiles to nothing', function () {
]);
});
it('names the M3 window size class for a Tailwind breakpoint', function () {
it('names the M3 breakpoint for a Tailwind prefix, with its 2.0.0 replacement', function () {
expect(fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/breakpoints'))->violations()))->toBe([
'breakpoints/layout.blade.php:1 Tailwind breakpoint `sm:`, use `medium:`',
'breakpoints/layout.blade.php:1 Tailwind breakpoint `md:`, use `medium:`',
'breakpoints/layout.blade.php:1 Tailwind breakpoint `lg:`, use `expanded:`',
'breakpoints/layout.blade.php:1 Tailwind breakpoint `xl:`, use `large:`',
'breakpoints/layout.blade.php:1 Tailwind breakpoint `2xl:`, use `extra-large:`',
'breakpoints/layout.blade.php:2 Tailwind breakpoint `max-sm:`, use `max-medium:`',
'breakpoints/layout.blade.php:2 Tailwind breakpoint `max-md:`, use `max-medium:`',
'breakpoints/layout.blade.php:2 Tailwind breakpoint `max-lg:`, use `max-expanded:`',
'breakpoints/layout.blade.php:2 Tailwind breakpoint `max-xl:`, use `max-large:`',
'breakpoints/layout.blade.php:2 Tailwind breakpoint `max-2xl:`, use `max-extra-large:`',
'breakpoints/layout.blade.php:3 Tailwind breakpoint `sm:`, use `medium:`',
'breakpoints/layout.blade.php:3 Tailwind breakpoint `md:`, use `medium:`',
"breakpoints/layout.blade.php:1 Tailwind breakpoint `sm:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 600px)` in your own CSS",
"breakpoints/layout.blade.php:1 Tailwind breakpoint `md:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 600px)` in your own CSS",
"breakpoints/layout.blade.php:1 Tailwind breakpoint `lg:` compiles to nothing — M3's expanded (840px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 840px)` in your own CSS",
"breakpoints/layout.blade.php:1 Tailwind breakpoint `xl:` compiles to nothing — M3's large (1200px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 1200px)` in your own CSS",
"breakpoints/layout.blade.php:1 Tailwind breakpoint `2xl:` compiles to nothing — M3's extra-large (1600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 1600px)` in your own CSS",
"breakpoints/layout.blade.php:2 Tailwind breakpoint `max-sm:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width < 600px)` in your own CSS",
"breakpoints/layout.blade.php:2 Tailwind breakpoint `max-md:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width < 600px)` in your own CSS",
"breakpoints/layout.blade.php:2 Tailwind breakpoint `max-lg:` compiles to nothing — M3's expanded (840px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width < 840px)` in your own CSS",
"breakpoints/layout.blade.php:2 Tailwind breakpoint `max-xl:` compiles to nothing — M3's large (1200px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width < 1200px)` in your own CSS",
"breakpoints/layout.blade.php:2 Tailwind breakpoint `max-2xl:` compiles to nothing — M3's extra-large (1600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width < 1600px)` in your own CSS",
]);
});
it('names the 2.0.0 replacement for a pseudo-class variant, but leaves a breakpoint prefix to its own finding', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/variant.blade.php'))->violations());
expect($violations)->toBe([
"tailwind/variant.blade.php:1 Tailwind variant class `hover:underline` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
"tailwind/variant.blade.php:1 Tailwind variant class `dark:opacity-50` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
"tailwind/variant.blade.php:2 Tailwind variant class `group-hover:flex` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
"tailwind/variant.blade.php:2 Tailwind variant class `focus:md:gap-2` compiles to nothing — a state is `:has()`/`aria-*`/`data-md-*` in your own CSS, a breakpoint a layout component's `hide-below`/`hide-from`/`stack-below` prop",
"tailwind/variant.blade.php:2 Tailwind breakpoint `md:` compiles to nothing — M3's medium (600px) is a layout component's `hide-below`/`hide-from`/`stack-below` prop, or `@media (width >= 600px)` in your own CSS",
]);
});
@@ -56,18 +68,18 @@ it('names the M3 corner for a Tailwind radius', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/scale/corners.blade.php'))->violations());
expect($violations)->toBe([
'scale/corners.blade.php:1 value outside the M3 scale `rounded-none`, use `rounded-corner-none`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-xs`, use `rounded-corner-xs`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-sm`, use `rounded-corner-sm`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-md`, use `rounded-corner-md`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-lg`, use `rounded-corner-lg`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-xl`, use `rounded-corner-xl`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-2xl`, use `rounded-corner-xxl`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-3xl`, use `rounded-corner-xxl`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-4xl`, use `rounded-corner-xxl`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-full`, use `rounded-corner-full`',
'scale/corners.blade.php:3 value outside the M3 scale `rounded-t-lg`, use `rounded-t-corner-lg`',
'scale/corners.blade.php:3 value outside the M3 scale `rounded-se-2xl`, use `rounded-se-corner-xxl`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-none` use `var(--md-sys-shape-corner-none)` in your own CSS, or `<x-surface corner="none">`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-xs` use `var(--md-sys-shape-corner-xs)` in your own CSS, or `<x-surface corner="xs">`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-sm` use `var(--md-sys-shape-corner-sm)` in your own CSS, or `<x-surface corner="sm">`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-md` use `var(--md-sys-shape-corner-md)` in your own CSS, or `<x-surface corner="md">`',
'scale/corners.blade.php:1 value outside the M3 scale `rounded-lg` use `var(--md-sys-shape-corner-lg)` in your own CSS, or `<x-surface corner="lg">`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-xl` use `var(--md-sys-shape-corner-xl)` in your own CSS, or `<x-surface corner="xl">`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-2xl` use `var(--md-sys-shape-corner-xxl)` in your own CSS, or `<x-surface corner="xxl">`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-3xl` use `var(--md-sys-shape-corner-xxl)` in your own CSS, or `<x-surface corner="xxl">`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-4xl` use `var(--md-sys-shape-corner-xxl)` in your own CSS, or `<x-surface corner="xxl">`',
'scale/corners.blade.php:2 value outside the M3 scale `rounded-full` use `var(--md-sys-shape-corner-full)` in your own CSS, or `<x-surface corner="full">`',
'scale/corners.blade.php:3 value outside the M3 scale `rounded-t-lg` use `var(--md-sys-shape-corner-lg)` in your own CSS, or `<x-surface corner="lg">`',
'scale/corners.blade.php:3 value outside the M3 scale `rounded-se-2xl` use `var(--md-sys-shape-corner-xxl)` in your own CSS, or `<x-surface corner="xxl">`',
]);
});
@@ -75,13 +87,13 @@ it('names the M3 elevation level for a Tailwind shadow', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/scale/elevation.blade.php'))->violations());
expect($violations)->toBe([
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-2xs`, use `shadow-elevation-1`',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-xs`, use `shadow-elevation-1`',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-sm`, use `shadow-elevation-1`',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-md`, use `shadow-elevation-2`',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-lg`, use `shadow-elevation-3`',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-xl`, use `shadow-elevation-4`',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-2xl`, use `shadow-elevation-5`',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-2xs` use `var(--md-sys-elevation-1)` in your own CSS',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-xs` use `var(--md-sys-elevation-1)` in your own CSS',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-sm` use `var(--md-sys-elevation-1)` in your own CSS',
'scale/elevation.blade.php:1 value outside the M3 scale `shadow-md` use `var(--md-sys-elevation-2)` in your own CSS',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-lg` use `var(--md-sys-elevation-3)` in your own CSS',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-xl` use `var(--md-sys-elevation-4)` in your own CSS',
'scale/elevation.blade.php:2 value outside the M3 scale `shadow-2xl` use `var(--md-sys-elevation-5)` in your own CSS',
]);
});
@@ -89,35 +101,35 @@ it('sends a size, a weight, a leading and a tracking to a type style', function
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/scale/type.blade.php'))->violations());
expect($violations)->toBe([
'scale/type.blade.php:1 value outside the M3 scale `text-xs`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-sm`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-base`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-lg`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-xl`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-2xl`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-9xl`, use a `type-*` style',
'scale/type.blade.php:2 value outside the M3 scale `font-thin`, use a `type-emphasized-*` style',
'scale/type.blade.php:2 value outside the M3 scale `font-extralight`, use a `type-emphasized-*` style',
'scale/type.blade.php:2 value outside the M3 scale `font-light`, use a `type-emphasized-*` style',
'scale/type.blade.php:2 value outside the M3 scale `font-normal`, use a `type-emphasized-*` style',
'scale/type.blade.php:2 value outside the M3 scale `font-medium`, use a `type-emphasized-*` style',
'scale/type.blade.php:3 value outside the M3 scale `font-semibold`, use a `type-emphasized-*` style',
'scale/type.blade.php:3 value outside the M3 scale `font-bold`, use a `type-emphasized-*` style',
'scale/type.blade.php:3 value outside the M3 scale `font-extrabold`, use a `type-emphasized-*` style',
'scale/type.blade.php:3 value outside the M3 scale `font-black`, use a `type-emphasized-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-none`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-tight`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-snug`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-normal`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-relaxed`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-loose`, use a `type-*` style',
'scale/type.blade.php:4 value outside the M3 scale `leading-6`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-tighter`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-tight`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-normal`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-wide`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-wider`, use a `type-*` style',
'scale/type.blade.php:5 value outside the M3 scale `tracking-widest`, use a `type-*` style',
'scale/type.blade.php:1 value outside the M3 scale `text-xs` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-sm` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-base` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-lg` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-xl` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-2xl` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:1 value outside the M3 scale `text-9xl` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:2 value outside the M3 scale `font-thin` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:2 value outside the M3 scale `font-extralight` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:2 value outside the M3 scale `font-light` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:2 value outside the M3 scale `font-normal` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:2 value outside the M3 scale `font-medium` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:3 value outside the M3 scale `font-semibold` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:3 value outside the M3 scale `font-bold` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:3 value outside the M3 scale `font-extrabold` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:3 value outside the M3 scale `font-black` use one of the `md-type-emphasized-*` classes (text.css), or a `md-type-*` size already at the right weight',
'scale/type.blade.php:4 value outside the M3 scale `leading-none` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-tight` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-snug` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-normal` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-relaxed` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-loose` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:4 value outside the M3 scale `leading-6` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-tighter` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-tight` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-normal` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-wide` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-wider` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
'scale/type.blade.php:5 value outside the M3 scale `tracking-widest` use one of the `md-type-*` classes (text.css), which set size, line height and tracking together',
]);
});
@@ -125,13 +137,13 @@ it('sends an easing and a duration to the motion tokens', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/scale/motion.blade.php'))->violations());
expect($violations)->toBe([
'scale/motion.blade.php:1 value outside the M3 scale `ease-linear`, use `ease-standard` or an `ease-spatial-*`/`ease-effects-*` with its duration',
'scale/motion.blade.php:1 value outside the M3 scale `ease-in`, use `ease-standard` or an `ease-spatial-*`/`ease-effects-*` with its duration',
'scale/motion.blade.php:1 value outside the M3 scale `ease-out`, use `ease-standard` or an `ease-spatial-*`/`ease-effects-*` with its duration',
'scale/motion.blade.php:1 value outside the M3 scale `ease-in-out`, use `ease-standard` or an `ease-spatial-*`/`ease-effects-*` with its duration',
'scale/motion.blade.php:2 value outside the M3 scale `duration-75`, use `duration-(--md-sys-motion-…-duration)` paired with its easing',
'scale/motion.blade.php:2 value outside the M3 scale `duration-300`, use `duration-(--md-sys-motion-…-duration)` paired with its easing',
'scale/motion.blade.php:2 value outside the M3 scale `duration-1000`, use `duration-(--md-sys-motion-…-duration)` paired with its easing',
'scale/motion.blade.php:1 value outside the M3 scale `ease-linear` — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition`',
'scale/motion.blade.php:1 value outside the M3 scale `ease-in` — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition`',
'scale/motion.blade.php:1 value outside the M3 scale `ease-out` — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition`',
'scale/motion.blade.php:1 value outside the M3 scale `ease-in-out` — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition`',
'scale/motion.blade.php:2 value outside the M3 scale `duration-75` — pair `var(--md-sys-motion-…-duration)` with its easing in your own `transition`',
'scale/motion.blade.php:2 value outside the M3 scale `duration-300` — pair `var(--md-sys-motion-…-duration)` with its easing in your own `transition`',
'scale/motion.blade.php:2 value outside the M3 scale `duration-1000` — pair `var(--md-sys-motion-…-duration)` with its easing in your own `transition`',
]);
});
@@ -145,6 +157,106 @@ it('finds a colour written as a value', function () {
]);
});
it('names the md-ink-*, divider/surface or token replacement for a dead M3 role utility', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/roles.blade.php'))->violations());
expect($violations)->toBe([
'tailwind/roles.blade.php:1 Tailwind ink role `text-on-surface-variant` compiles to nothing — use `md-ink-variant` (text.css)',
'tailwind/roles.blade.php:1 Tailwind line role `border-outline-variant` compiles to nothing — use `<x-divider>` or `<x-surface outlined>` for a line, not a border utility',
'tailwind/roles.blade.php:2 Tailwind surface role `bg-surface-container` compiles to nothing — use `<x-surface level="surface-container">`',
'tailwind/roles.blade.php:2 Tailwind role utility `bg-primary` compiles to nothing — use `var(--md-sys-color-*)` in your own CSS',
]);
});
it('sends flex, grid and gap utilities to the layout components', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/layout.blade.php'))->violations());
$hint = 'compiles to nothing — use `<x-row>`, `<x-grid>` or `<x-stack>` and their `gap`/`align`/`justify`/`columns` props';
expect($violations)->toBe([
"tailwind/layout.blade.php:1 Tailwind layout utility `flex` {$hint}",
"tailwind/layout.blade.php:1 Tailwind layout utility `grid` {$hint}",
"tailwind/layout.blade.php:1 Tailwind layout utility `inline-flex` {$hint}",
"tailwind/layout.blade.php:1 Tailwind layout utility `flex-col` {$hint}",
"tailwind/layout.blade.php:2 Tailwind layout utility `grid-cols-3` {$hint}",
"tailwind/layout.blade.php:2 Tailwind layout utility `col-span-2` {$hint}",
"tailwind/layout.blade.php:2 Tailwind layout utility `items-center` {$hint}",
"tailwind/layout.blade.php:2 Tailwind layout utility `justify-between` {$hint}",
"tailwind/layout.blade.php:2 Tailwind layout utility `gap-4` {$hint}",
]);
});
it('sends padding, margin and space-between utilities to a layout component\'s gap/padding prop', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/spacing.blade.php'))->violations());
$hint = "compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS";
expect($violations)->toBe([
"tailwind/spacing.blade.php:1 Tailwind spacing utility `p-4` {$hint}",
"tailwind/spacing.blade.php:1 Tailwind spacing utility `px-2` {$hint}",
"tailwind/spacing.blade.php:1 Tailwind spacing utility `mt-2` {$hint}",
"tailwind/spacing.blade.php:1 Tailwind spacing utility `space-y-4` {$hint}",
"tailwind/spacing.blade.php:2 Tailwind spacing utility `m-4` {$hint}",
"tailwind/spacing.blade.php:2 Tailwind spacing utility `space-x-2` {$hint}",
]);
});
it('sends width and height utilities to a literal length, since M3 keeps no size scale', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/sizing.blade.php'))->violations());
$hint = 'compiles to nothing — M3 keeps no size scale; write the literal length in your own CSS, or use `<x-pane width>`/`<x-grid min-item>` where it fits';
expect($violations)->toBe([
"tailwind/sizing.blade.php:1 Tailwind sizing utility `w-full` {$hint}",
"tailwind/sizing.blade.php:1 Tailwind sizing utility `h-screen` {$hint}",
"tailwind/sizing.blade.php:1 Tailwind sizing utility `min-w-0` {$hint}",
"tailwind/sizing.blade.php:1 Tailwind sizing utility `max-w-md` {$hint}",
"tailwind/sizing.blade.php:2 Tailwind sizing utility `size-4` {$hint}",
]);
});
it('sends hidden to hide-below/hide-from and every other display utility to a plain CSS rule', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/display.blade.php'))->violations());
expect($violations)->toBe([
"tailwind/display.blade.php:1 Tailwind's `hidden` compiles to nothing — use a layout component's `hide-below`/`hide-from` prop, or the reset's `[hidden]` attribute",
'tailwind/display.blade.php:1 Tailwind display utility `block` compiles to nothing — write the `display` rule in your own CSS',
'tailwind/display.blade.php:2 Tailwind display utility `inline-block` compiles to nothing — write the `display` rule in your own CSS',
'tailwind/display.blade.php:2 Tailwind display utility `invisible` compiles to nothing — write the `display` rule in your own CSS',
]);
});
it('sends text-layout utilities to their md-* class', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/text.blade.php'))->violations());
expect($violations)->toBe([
"tailwind/text.blade.php:1 Tailwind's `text-center` compiles to nothing — use `md-text-center` (text.css)",
"tailwind/text.blade.php:1 Tailwind's `truncate` compiles to nothing — use `md-truncate` (text.css)",
"tailwind/text.blade.php:2 Tailwind's `sr-only` compiles to nothing — use `md-visually-hidden` (text.css)",
"tailwind/text.blade.php:2 Tailwind's `whitespace-nowrap` compiles to nothing — use `md-nowrap` (text.css)",
"tailwind/text.blade.php:2 Tailwind's `line-clamp-2` compiles to nothing — use `md-line-clamp-2` (text.css)",
"tailwind/text.blade.php:2 Tailwind's `tabular-nums` compiles to nothing — use `md-tabular` (text.css)",
]);
});
it('exempts a class the application\'s own CSS declares, even when it is Tailwind-shaped', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/exempt'))->violations());
expect($violations)->toBe([
"exempt/view.blade.php:1 Tailwind spacing utility `mt-2` compiles to nothing — use a layout component's `gap`/`padding` prop (`<x-stack gap>`, `<x-surface padding>`), or `var(--md-sys-measurement-space*)` in your own CSS",
]);
});
it('reports an arbitrary [] value once, whatever utility it modifies, and strips a trailing !', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/arbitrary.blade.php'))->violations());
expect($violations)->toBe([
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `top-[10px]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
'tailwind/arbitrary.blade.php:1 Tailwind arbitrary value `grid-cols-[1fr_2fr]` compiles to nothing — write the literal value in your own CSS, or use an M3 token',
'tailwind/arbitrary.blade.php:2 Tailwind layout utility `flex` compiles to nothing — use `<x-row>`, `<x-grid>` or `<x-stack>` and their `gap`/`align`/`justify`/`columns` props',
]);
});
it('bans the two absolutes when asked', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/colour'))->forbidAbsolutes()->violations());
@@ -1,5 +1,3 @@
<div class="sm:flex md:grid lg:hidden xl:block 2xl:contents">
<span class="max-sm:hidden max-md:flex max-lg:grid max-xl:block max-2xl:contents"></span>
<p class="sm:hover:underline focus:md:gap-2">stacked variants count too</p>
<span class="medium:flex expanded:grid large:block extra-large:contents max-medium:hidden">M3's own are fine</span>
</div>
@@ -2,5 +2,5 @@
<span class="fill-[oklch(0.7_0.1_250)] ring-[color-mix(in_oklab,var(--x)_50%,transparent)]"></span>
<span class="bg-white text-black border-white"></span>
<span class="text-on-surface/60 bg-on-surface/12 border-outline/38"></span>
<span class="bg-primary text-on-primary border-outline-variant">M3's own are fine</span>
<span class="md-ink md-type-body-md">M3's own are fine</span>
</div>
@@ -0,0 +1,3 @@
.gap-4 {
gap: 1rem;
}
@@ -0,0 +1 @@
<div class="gap-4 mt-2"></div>
@@ -0,0 +1,3 @@
<div class="top-[10px] grid-cols-[1fr_2fr]">
<span class="flex!"></span>
</div>
@@ -0,0 +1,3 @@
<div class="hidden block">
<span class="inline-block invisible"></span>
</div>
@@ -0,0 +1,3 @@
<div class="flex grid inline-flex flex-col">
<span class="grid-cols-3 col-span-2 items-center justify-between gap-4"></span>
</div>
@@ -0,0 +1,2 @@
<div class="text-on-surface-variant border-outline-variant"></div>
<span class="bg-surface-container bg-primary"></span>
@@ -0,0 +1,3 @@
<div class="w-full h-screen min-w-0 max-w-md">
<span class="size-4"></span>
</div>
@@ -0,0 +1,3 @@
<div class="p-4 px-2 mt-2 space-y-4">
<span class="m-4 space-x-2"></span>
</div>
@@ -0,0 +1,3 @@
<p class="text-center truncate">
<span class="sr-only whitespace-nowrap line-clamp-2 tabular-nums"></span>
</p>
@@ -0,0 +1,3 @@
<div class="hover:underline dark:opacity-50">
<span class="group-hover:flex focus:md:gap-2"></span>
</div>