Plan step 37: all.css replaces the two interim import lists with one entry for an application that wants everything — the foundation, every layout stylesheet and every component stylesheet, grouped under the same block comments components.css used, plus a Layout block. It also directly imports the three files nothing imported by name before (layout/spacing.css, layout/visibility.css, components/selection.css), so every file under components/ and layout/ is now one @import away. Every test that read a block of components.css or layout.css now reads the matching block of all.css through one shared helper (allCssBlock(), in tests/Pest.php so it loads for any test run) instead of repeating the same substr() search in each file. StylesheetsTest.php's shape, Tailwind-free and breakpoint checks, previously run twice (once for the foundation, once for the layout tree), now run once over the whole tree all.css reaches, since every component and layout stylesheet is plain CSS after step 36; a new test asserts all.css imports everything under components/ and layout/ exactly once. The Workbench imports all.css in place of layout.css and components.css. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
122 lines
6.0 KiB
PHP
122 lines
6.0 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses;
|
|
|
|
/**
|
|
* The containment components, rewritten without Tailwind (plan step 36): each view renders
|
|
* `data-md-*` attributes and no class list of its own beyond the interaction and text classes
|
|
* (tests/Support/ViewClasses.php), and each has a stylesheet in `material.components` that imports
|
|
* the stylesheets of the components its view renders, writes its values from the tokens and its
|
|
* breakpoints as px range queries, and is imported from the "Containment" block of all.css.
|
|
*
|
|
* The dataset grew by one name per component commit, the same rule InputStylesheetsTest.php and
|
|
* ActionStylesheetsTest.php follow. The error layout, whose view is not a component, has the same
|
|
* checks in tests/Feature/ErrorPagesTest.php.
|
|
*/
|
|
dataset('containment components', [
|
|
'divider',
|
|
'collapse',
|
|
'card',
|
|
'list',
|
|
'list-item',
|
|
'modal',
|
|
'drawer',
|
|
'bottom-sheet',
|
|
'carousel-item',
|
|
'carousel',
|
|
]);
|
|
|
|
/**
|
|
* Fails unless every media query in the stylesheet writes its lengths in px, at one of M3's four
|
|
* breakpoints. `rem` and `em` would grow with the reader's text size, which a breakpoint must not.
|
|
*/
|
|
function assertContainmentBreakpointsInPx(ComponentStylesheet $css, string $label): void
|
|
{
|
|
foreach ($css->mediaQueries() as $query) {
|
|
preg_match_all('/\(([^()]*)\)/', $query, $features);
|
|
|
|
foreach ($features[1] as $feature) {
|
|
preg_match_all('/(\d*\.?\d+)(px|rem|em)\b/', $feature, $lengths, PREG_SET_ORDER);
|
|
|
|
foreach ($lengths as [, $number, $unit]) {
|
|
expect($unit)->toBe('px', "{$label}: {$query}");
|
|
expect(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$label}: {$query} is not at an M3 breakpoint");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Colours are roles, shadows are elevation levels or a ring, type is a type style, and breakpoints
|
|
* are px, at one of M3's four.
|
|
*/
|
|
function assertContainmentTokensAndPxBreakpoints(ComponentStylesheet $css, string $label): void
|
|
{
|
|
$source = (string) preg_replace('~/\*.*?\*/~s', '', $css->css);
|
|
|
|
expect($source)->not->toMatch('/#[0-9a-f]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(/i')
|
|
->not->toMatch('/\bfont:(?!\s*var\(--md-sys-typescale-)/')
|
|
->not->toMatch('/\btransition[a-z-]*:[^;]*(?:\d+m?s\b|\bease\b|ease-in|ease-out|cubic-bezier)/');
|
|
|
|
// A box-shadow is `none`, an elevation level, or a ring: `[inset] 0 0 0 <n>px` in a colour role.
|
|
$colour = 'var\(--md-sys-color-[a-z-]+\)|color-mix\(in srgb, var\(--md-sys-color-[a-z-]+\) [^;]+?, transparent\)';
|
|
|
|
preg_match_all('/\bbox-shadow:\s*([^;]+);/', $source, $shadows, PREG_SET_ORDER);
|
|
|
|
foreach ($shadows as [, $value]) {
|
|
expect(trim($value))->toMatch("/^(?:none|var\\(--md-sys-elevation-[0-5]\\)|(?:inset )?0 0 0 \\d+px (?:{$colour}))$/", "{$label}: box-shadow: {$value} is not none, an elevation or a ring in a colour role");
|
|
}
|
|
|
|
assertContainmentBreakpointsInPx($css, $label);
|
|
}
|
|
|
|
it('draws the component from a stylesheet shaped like every package stylesheet', function (string $name) {
|
|
$css = ComponentStylesheet::read($name);
|
|
|
|
expect($css->css)->toStartWith('/*')
|
|
->and($css->statements()[0] ?? null)->toBe('@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;')
|
|
->and(array_slice($css->statements(), 1))->each->toMatch('/^@import \'\.\/[a-z-]+\.css\';$/')
|
|
->and($css->blocks())->each->toBe('@layer material.components')
|
|
->and($css->css)->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b|--(?:theme|spacing|alpha)\(|\btheme\(/');
|
|
|
|
foreach ($css->imports() as $import) {
|
|
expect(is_file(dirname(ComponentStylesheet::path($name)).'/'.$import))->toBeTrue("{$name}.css imports {$import}, which does not exist");
|
|
}
|
|
})->with('containment components');
|
|
|
|
it('imports the stylesheet of every component its view renders', function (string $name) {
|
|
preg_match_all('/<x-livewire-material::([a-z-]+)/', File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php"), $tags);
|
|
|
|
$rendered = collect($tags[1])->unique()
|
|
// A component still written for Tailwind has no stylesheet of its own to import yet; its
|
|
// old stylesheet, if it has one, lives in tailwind.css without the layer statement.
|
|
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)) && str_contains(File::get(ComponentStylesheet::path($tag)), '@layer material.components'))
|
|
->map(fn (string $tag): string => "./{$tag}.css")
|
|
->values()
|
|
->all();
|
|
|
|
expect(array_values(array_diff($rendered, ComponentStylesheet::read($name)->imports())))->toBe([]);
|
|
})->with('containment components');
|
|
|
|
it('writes no class list into the view but the interaction and text classes', function (string $name) {
|
|
expect(ViewClasses::violations(File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php")))->toBe([]);
|
|
})->with('containment components');
|
|
|
|
it('takes its values from the tokens and its breakpoints in px', function (string $name) {
|
|
assertContainmentTokensAndPxBreakpoints(ComponentStylesheet::read($name), "{$name}.css");
|
|
})->with('containment components');
|
|
|
|
it('is imported from the containment block of all.css', function (string $name) {
|
|
expect(allCssBlock('Containment'))->toContain("@import './components/{$name}.css';");
|
|
})->with('containment components');
|
|
|
|
it('scopes every element-wide selector to a data-md hook', function (string $name) {
|
|
// `all.css` bundles every component stylesheet into every page, so a bare `html`, `body`,
|
|
// `dialog` or `:root` rule would restyle the application's own pages.
|
|
$source = (string) preg_replace('~/\*.*?\*/~s', '', ComponentStylesheet::read($name)->css);
|
|
|
|
expect($source)->not->toMatch('/(?:^|[,{};]\s*)(?:html|body|dialog|:root)(?![\w-])(?!\[data-md-|:has\(> \[data-md-)/m');
|
|
})->with('containment components');
|