Take Tailwind out of the package, and its detection out of the guard
tests / feature (8.4) (push) Successful in 2m0s
tests / feature (8.5) (push) Successful in 2m0s
tests / browser (chrome, chromium) (push) Failing after 8m3s
tests / browser (firefox, firefox) (push) Failing after 12m58s
tests / browser (safari, webkit) (push) Failing after 13m8s
tests / feature (8.4) (push) Successful in 2m0s
tests / feature (8.5) (push) Successful in 2m0s
tests / browser (chrome, chromium) (push) Failing after 8m3s
tests / browser (firefox, firefox) (push) Failing after 12m58s
tests / browser (safari, webkit) (push) Failing after 13m8s
Tailwind left the stack in 2.0.0, but the package still carried about 330 mentions of it. What the guard's Tailwind detection protected — a class that compiles to nothing — is now protected by a check that does not care where a dead class came from. DesignGuard: about 500 lines of Tailwind tables, scales, palettes and "2.0.0 replacement" hints give way to one check — a class a view or PHP file writes that neither the application's stylesheets nor the package's own declare. It catches a utility of any framework, a typo and a class whose rules were deleted alike, so it also found two classes ReStride draws nothing with. A stylesheet has to be in reach for it: the `.css` files among the scanned paths, or what the `missingStylesheets()` entry imports. The class reader no longer mistakes an array index for a class list (`$block['base']`), and it reads the array a class helper is given, where it read nothing before. The package's own three Tailwind self-guards go with it. Only their one unique check stays, as a test of its own: every `matchMedia` width in resources/js is an M3 breakpoint. The pagination views are `material.blade.php` and `simple-material.blade.php`; only Laravel's and Livewire's default theme names ever made them `tailwind`. The provider sets `Paginator`'s default views and switches `livewire.pagination_theme` to `material` when it is still Livewire's own default, so no application can forget the config; a theme an application chose, and a component's own `$paginationTheme` or `paginationView()`, still win. The rest is prose: the layer-order guidance for an application that still builds Tailwind, the Tailwind wording in the README, the Boost guidelines and the development skill, and about 25 "this used to be a Tailwind utility" comments, along with every "plan step NN" pointer into a gitignored folder. The reset keeps its credit, and NOTICE now carries it too. Feature suite 1159 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
247c596c3a
commit
fb7007c976
@@ -5,14 +5,13 @@ use NoNameWeb\LivewireMaterial\Support\Layout;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
/**
|
||||
* The package's stylesheets are plain CSS in `material.*` layers, with no Tailwind in them. Every
|
||||
* one opens with the same layer statement, so the order holds whichever file a bundler reaches
|
||||
* first; then plain imports (an `@import` cannot sit inside a layer block, and `layer()` on it
|
||||
* would nest the layer twice); then its own `@layer material.<x>` blocks. The only rules outside
|
||||
* a layer are foundation/hidden.css's two `!important` ones.
|
||||
* The package's stylesheets are plain CSS in `material.*` layers. Every one opens with the same
|
||||
* layer statement, so the order holds whichever file a bundler reaches first; then plain imports
|
||||
* (an `@import` cannot sit inside a layer block, and `layer()` on it would nest the layer twice);
|
||||
* then its own `@layer material.<x>` blocks. The only rules outside a layer are
|
||||
* foundation/hidden.css's two `!important` ones.
|
||||
*
|
||||
* Every package component and layout stylesheet is plain CSS now (plan step 36), and Tailwind
|
||||
* itself is gone from the package (plan step 39), so the shape, Tailwind-free and breakpoint checks
|
||||
* Every package component and layout stylesheet is plain CSS, so the shape and breakpoint checks
|
||||
* below run over the whole tree `all.css` reaches (foundation, layout and components together)
|
||||
* rather than the foundation alone, and — one file it does not reach — `showcase.css`, checked on
|
||||
* its own further down. A component's own values (colours as roles, shadows as elevation levels,
|
||||
@@ -64,7 +63,7 @@ function stylesheetImports(string $file): array
|
||||
|
||||
/**
|
||||
* A stylesheet and every stylesheet it reaches through `@import`, each once, in the order a
|
||||
* bundler meets them. `tailwindcss` itself is a package, not a file, and is left out.
|
||||
* bundler meets them. An import that is not relative (a package name, a URL) is left out.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
@@ -105,9 +104,7 @@ function foundationStylesheets(): array
|
||||
|
||||
/**
|
||||
* Everything all.css reaches: the foundation, every layout stylesheet and every component
|
||||
* stylesheet, each once. Every one of them is plain CSS (plan step 36), and Tailwind itself is
|
||||
* gone from the package (plan step 39) — `tailwindcss`, `tailwind.css` and its two token files no
|
||||
* longer exist, so there is nothing left for this tree to exclude.
|
||||
* stylesheet, each once. Every one of them is plain CSS.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
@@ -131,8 +128,7 @@ it('brings every foundation file and text.css into the foundation, and nothing e
|
||||
expect($parts->diff($foundation)->values()->all())->toBe([])
|
||||
->and(array_map(fn (string $import): string => basename($import), stylesheetImports(stylesheetPath('foundation.css'))))
|
||||
// In the order of their layers, the rules outside every layer beside the reset — the seven
|
||||
// token files imported directly (plan step 33: foundation/tokens.css held only these
|
||||
// imports, and only foundation.css reached it, so it was folded in here).
|
||||
// token files imported directly.
|
||||
->toBe(['reset.css', 'hidden.css', 'scheme.css', 'shape.css', 'elevation.css', 'motion.css', 'type.css', 'state.css', 'spacing.css', 'base.css', 'interaction.css', 'text.css'])
|
||||
->and($foundation)
|
||||
->toContain('tokens/scheme.css', 'tokens/shape.css', 'tokens/elevation.css', 'tokens/motion.css', 'tokens/type.css', 'tokens/state.css', 'tokens/spacing.css', 'tokens/font.css')
|
||||
@@ -140,8 +136,8 @@ it('brings every foundation file and text.css into the foundation, and nothing e
|
||||
});
|
||||
|
||||
it('paints the page in surface and on-surface, in the brand typeface, smoothed in grayscale', function () {
|
||||
// An application drops Tailwind's `antialiased` with nothing to replace it: the showcase and the
|
||||
// error pages, which set it themselves before, read it from here too.
|
||||
// An application gets it with no class of its own needed: the showcase and the error pages,
|
||||
// which set it themselves before, read it from here too.
|
||||
$base = (string) preg_replace('~/\*.*?\*/~s', '', (string) file_get_contents(stylesheetPath('foundation/base.css')));
|
||||
|
||||
expect($base)->toMatch('/html \{\s*background-color: var\(--md-sys-color-surface\);\s*color: var\(--md-sys-color-on-surface\);\s*font-family: var\(--md-ref-typeface-brand\);\s*-webkit-font-smoothing: antialiased;\s*-moz-osx-font-smoothing: grayscale;\s*\}/')
|
||||
@@ -163,9 +159,9 @@ it('names the monospace faces once, as a typeface token the reset, the field and
|
||||
});
|
||||
|
||||
it('leaves no stylesheet under resources/css/ outside all.css or showcase.css', function () {
|
||||
// The full tree now that Tailwind is gone (plan step 39): every .css file on disk, not just the
|
||||
// ones a bundler happens to reach — a stray or orphaned file would show up here even though
|
||||
// nothing imports it, which the checks above (walking @import from all.css) cannot catch.
|
||||
// The full tree: every .css file on disk, not just the ones a bundler happens to reach — a
|
||||
// stray or orphaned file would show up here even though nothing imports it, which the checks
|
||||
// above (walking @import from all.css) cannot catch.
|
||||
$onDisk = collect(File::allFiles(stylesheetPath()))
|
||||
->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'css')
|
||||
->map(fn (SplFileInfo $file): string => stylesheetName((string) $file->getRealPath()))
|
||||
@@ -257,15 +253,7 @@ it('defines x-cloak once, reached from both the foundation and all.css', functio
|
||||
->and(allStylesheets())->toContain(stylesheetPath('foundation/hidden.css'));
|
||||
});
|
||||
|
||||
it('writes no Tailwind directive anywhere all.css reaches', function () {
|
||||
foreach (allStylesheets() as $file) {
|
||||
expect(ComponentStylesheet::withoutComments(File::get($file)))
|
||||
->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/', stylesheetName($file))
|
||||
->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/', stylesheetName($file));
|
||||
}
|
||||
});
|
||||
|
||||
// No Tailwind-shaped class anywhere the package ships or tests itself is
|
||||
// A class no stylesheet declares, anywhere the package ships or tests itself, is
|
||||
// tests/Feature/DesignGuardTest.php's: one of its tests scans resources/views, resources/js, src
|
||||
// and the Workbench, another scans tests/Browser and tests/Feature, so nothing here repeats
|
||||
// either.
|
||||
@@ -309,6 +297,39 @@ it('writes a media query, anywhere all.css reaches, only at M3\'s breakpoints, i
|
||||
}
|
||||
});
|
||||
|
||||
it('writes every matchMedia width in resources/js at an M3 breakpoint', function () {
|
||||
// The one check BreakpointsTest.php (deleted) covered that nothing else did: a script that
|
||||
// widens or narrows window.matchMedia() by hand, at a width M3 does not know, rather than
|
||||
// asking resources/js/breakpoints.js for the number. Isolated the same way that test isolated
|
||||
// a media query's own condition — only the text inside matchMedia()'s call, up to its first
|
||||
// closing quote or backtick — so a real width sitting elsewhere in the file, in a plain
|
||||
// declaration rather than a query condition (a `min(40rem, 70dvh)`-style cap; none exists in
|
||||
// resources/js today), is never mistaken for a breakpoint.
|
||||
$files = collect(File::allFiles(__DIR__.'/../../resources/js'))
|
||||
->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'js');
|
||||
|
||||
expect($files)->not->toBeEmpty();
|
||||
|
||||
foreach ($files as $file) {
|
||||
preg_match_all('/matchMedia\(\s*[\'"`][^\'"`]*/', $file->getContents(), $conditions);
|
||||
|
||||
foreach ($conditions[0] as $condition) {
|
||||
preg_match_all('/(\d*\.?\d+)(rem|px)/i', $condition, $lengths, PREG_SET_ORDER);
|
||||
|
||||
foreach ($lengths as [, $number, $unit]) {
|
||||
// breakpoints.js itself never writes a literal number — from() and upTo() build
|
||||
// the string from the shared table (`${width(name)}px`) — so today's tree has none
|
||||
// to check; this only guards whichever file writes one by hand next.
|
||||
$allowed = strtolower($unit) === 'px' ? ['600', '840', '1200', '1600'] : ['37.5', '52.5', '75', '100'];
|
||||
|
||||
expect(in_array($number, $allowed, true))->toBeTrue(
|
||||
$file->getRelativePathname().': '.trim($condition).' is not at an M3 breakpoint'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('declares M3\'s spacing scale as the reference gives it', function () {
|
||||
preg_match_all(
|
||||
'/^\|\s*\**space(\d+)\**\s*\|\s*\**([\d.]+)×\**\s*\|\s*\**(\d+)dp/m',
|
||||
@@ -526,10 +547,10 @@ it('imports, from every layout stylesheet, the stylesheet of each component its
|
||||
expect($checked)->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Every layout stylesheet's header, layer statement and plain-imports shape, and its freedom from
|
||||
// Tailwind and from a media query off an M3 breakpoint, are covered by the two all.css-wide tests
|
||||
// above (allStylesheets() reaches every layout file too) — a dedicated layout-only version of
|
||||
// either would only repeat that same check on a subset already checked.
|
||||
// Every layout stylesheet's header, layer statement and plain-imports shape, and its media
|
||||
// queries off an M3 breakpoint, are covered by the two all.css-wide tests above (allStylesheets()
|
||||
// reaches every layout file too) — a dedicated layout-only version of either would only repeat
|
||||
// that same check on a subset already checked.
|
||||
|
||||
it('keeps every layout rule in material.layout, and the visibility props in material.visibility', function () {
|
||||
// layoutStylesheets() follows every @import, so it now reaches component stylesheets a layout
|
||||
@@ -607,10 +628,6 @@ it('hides an element below and from every breakpoint but compact, and nothing el
|
||||
});
|
||||
|
||||
it('gives every md-type class exactly font, letter-spacing and font-variation-settings', function () {
|
||||
// Tailwind's tokens/utilities.css used to declare the same 30 styles as `type-*` utility
|
||||
// blocks, and this test proved text.css's `.md-type-*` classes matched them; deleted with
|
||||
// Tailwind (plan step 39), text.css is the only copy left, so the shape it gives each class is
|
||||
// asserted directly rather than cross-checked against a second copy.
|
||||
preg_match_all('/\.md-(type-[\w-]+) \{\n(.*?)\n {4}\}/s', File::get(stylesheetPath('text.css')), $matches, PREG_SET_ORDER);
|
||||
|
||||
$classes = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => ComponentStylesheet::flatDeclarations($match[2])]);
|
||||
@@ -672,27 +689,16 @@ it('ships exactly the documented text classes, each ink an M3 role', function ()
|
||||
}
|
||||
});
|
||||
|
||||
// A test used to prove foundation/interaction.css's `.md-*` classes matched tokens/utilities.css's
|
||||
// utility blocks byte for byte apart from two deliberate differences, because the components
|
||||
// still written in Tailwind read the utilities while every other component read the classes.
|
||||
// Deleted with Tailwind (plan step 39): every component reads the classes now, there is no second
|
||||
// copy left to drift from, and interaction.css's own StylesheetsTest coverage above (the layer it
|
||||
// writes into, no Tailwind directive) already holds it to the same shape every other foundation
|
||||
// file keeps to.
|
||||
|
||||
it('builds the package in the Workbench\'s one CSS entry, with no Tailwind left to sit between', function () {
|
||||
it('opens the Workbench\'s one CSS entry with the layer statement, then all.css, showcase.css and the scheme', function () {
|
||||
$app = ComponentStylesheet::withoutComments(File::get(__DIR__.'/../../workbench/resources/css/app.css'));
|
||||
|
||||
// Opens by ordering the layers, so the order holds whichever the page links first — an
|
||||
// application with Tailwind of its own still opens its own entry the same way (its header).
|
||||
// Opens by ordering the layers, so the order holds whichever the page links first.
|
||||
expect(trim($app))->toStartWith(MATERIAL_LAYER_STATEMENT)
|
||||
->toContain("@import '../../../resources/css/all.css';")
|
||||
->toContain("@import '../../../resources/css/showcase.css';")
|
||||
->toContain("@import './material-scheme.css';")
|
||||
->not->toMatch('/@(?:tailwind|source|theme|utility|variant|custom-variant|apply|config|plugin|reference)\b/')
|
||||
->and(File::get(__DIR__.'/../../vite.config.js'))
|
||||
->toContain("input: ['workbench/resources/css/app.css', 'workbench/resources/js/app.js']")
|
||||
->not->toContain('tailwindcss')
|
||||
->and(config('livewire-material.showcase.vite'))->toBe([
|
||||
'workbench/resources/css/app.css',
|
||||
'workbench/resources/js/app.js',
|
||||
@@ -705,10 +711,7 @@ it('shapes showcase.css like a package stylesheet, with its documented unlayered
|
||||
$items = ComponentStylesheet::items(ComponentStylesheet::withoutComments($css));
|
||||
|
||||
expect($css)->toStartWith('/*', 'showcase.css has no header comment')
|
||||
->and($items[0]['statement'] ?? null)->toBe(MATERIAL_LAYER_STATEMENT, 'showcase.css does not open with the layer statement')
|
||||
->and(ComponentStylesheet::withoutComments($css))
|
||||
->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/')
|
||||
->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/');
|
||||
->and($items[0]['statement'] ?? null)->toBe(MATERIAL_LAYER_STATEMENT, 'showcase.css does not open with the layer statement');
|
||||
|
||||
$blocks = false;
|
||||
$unlayered = [];
|
||||
|
||||
Reference in New Issue
Block a user