Draw the loading indicator without Tailwind
Plan step 36. <x-loading> renders data-md-loading and data-md-contained, and loading.css in material.components draws the 48px indicator in primary, the contained circle and the still shape under reduced motion. The view no longer reads the caller's classes to decide its size and colour: a caller's utility outranks the package's layer on its own. ActionStylesheetsTest checks the group's stylesheets for the package's shape (layer statement, plain imports, material.components, tokens, px breakpoints, imports of rendered components) and its views for class lists; tests/Support/ComponentStylesheet reads a rule's declarations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
d7653aa6d0
commit
6c7414d60e
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
/**
|
||||
* The actions and communication components, rewritten without Tailwind (plan step 36): each view
|
||||
* renders `data-md-*` attributes and no class list of its own, 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
|
||||
* "Actions and communication" block of components.css.
|
||||
*/
|
||||
dataset('action components', [
|
||||
'loading',
|
||||
]);
|
||||
|
||||
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('action 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.
|
||||
->filter(fn (string $tag): bool => is_file(ComponentStylesheet::path($tag)))
|
||||
->map(fn (string $tag): string => "./{$tag}.css")
|
||||
->values()
|
||||
->all();
|
||||
|
||||
expect(array_values(array_diff($rendered, ComponentStylesheet::read($name)->imports())))->toBe([]);
|
||||
})->with('action components');
|
||||
|
||||
it('writes no class list into the view', function (string $name) {
|
||||
$view = File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php");
|
||||
|
||||
// A class the caller hands in (`hint-class`, `icon-class`, a slot's attributes) passes through.
|
||||
expect($view)->not->toMatch('/\bclass="(?!\{\{ \$)|@class\(|->class\(|toCssClasses|(?:x-bind)?:class=/');
|
||||
})->with('action components');
|
||||
|
||||
it('takes its values from the tokens and its breakpoints in px', function (string $name) {
|
||||
$css = ComponentStylesheet::read($name);
|
||||
$source = (string) preg_replace('~/\*.*?\*/~s', '', $css->css);
|
||||
|
||||
// Colours are roles, shadows are elevation levels, type is a type style, motion is a spring.
|
||||
expect($source)->not->toMatch('/#[0-9a-f]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(/i')
|
||||
->not->toMatch('/\bbox-shadow:\s*(?!none|var\(--md-)/')
|
||||
->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)/');
|
||||
|
||||
foreach ($css->mediaQueries() as $query) {
|
||||
preg_match_all('/(\d*\.?\d+)(px|rem|em)\b/', $query, $lengths, PREG_SET_ORDER);
|
||||
|
||||
foreach ($lengths as [, $number, $unit]) {
|
||||
expect($unit)->toBe('px', "{$name}.css: {$query}")
|
||||
->and(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$name}.css: {$query} is not at an M3 breakpoint");
|
||||
}
|
||||
}
|
||||
})->with('action components');
|
||||
|
||||
it('is imported from the actions and communication block of components.css', function (string $name) {
|
||||
$components = File::get(__DIR__.'/../../../resources/css/components.css');
|
||||
$block = substr($components, (int) strpos($components, '/* Actions and communication */'));
|
||||
$block = substr($block, 0, (int) strpos($block, '/* Inputs, selection and data */'));
|
||||
|
||||
expect($block)->toContain("@import './components/{$name}.css';");
|
||||
})->with('action components');
|
||||
Reference in New Issue
Block a user