Review leftover (plan step 36): none of the three foundation/shared stylesheets was in a group dataset, so their shape, token and breakpoint rules went unchecked. icon and shape have views of their own and sit in components.css's first block, so they join ActionStylesheetsTest's dataset outright, with that file's block check narrowed to look for them there instead of the actions block. selection.css has no view (checkbox, radio and toggle each import it) and components.css never imports it directly, so it follows navigation-item.css's pattern in InputStylesheetsTest: a second, stylesheet-only dataset alongside the view-based one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
111 lines
5.4 KiB
PHP
111 lines
5.4 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses;
|
|
|
|
/**
|
|
* The actions and communication 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
|
|
* "Actions and communication" block of components.css.
|
|
*
|
|
* `icon` and `shape` are the two foundation components (`<x-icon>`, `<x-shape>`) — no group's view
|
|
* renders them exclusively, so they were in no group's dataset at all. They follow the same shape,
|
|
* token and view-class rules as every other component, so they run with this group's checks too;
|
|
* only "is imported from the actions and communication block" needs to read the two of them from
|
|
* components.css's first block ("Foundation components") instead, since that is where they sit.
|
|
*/
|
|
dataset('action components', [
|
|
'icon',
|
|
'shape',
|
|
'loading',
|
|
'tooltip',
|
|
'badge',
|
|
'button',
|
|
'group',
|
|
'button-group',
|
|
'split-button',
|
|
'fab',
|
|
'menu-separator',
|
|
'menu-group',
|
|
'menu-item',
|
|
'menu',
|
|
'fab-menu-item',
|
|
'fab-menu',
|
|
'rich-tooltip',
|
|
'toast',
|
|
'progress',
|
|
'alert',
|
|
'stat',
|
|
'empty-state',
|
|
]);
|
|
|
|
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; 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('action 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('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, or icon and shape from the foundation block before it', function (string $name) {
|
|
$components = File::get(__DIR__.'/../../../resources/css/components.css');
|
|
|
|
if (in_array($name, ['icon', 'shape'], true)) {
|
|
$block = substr($components, (int) strpos($components, '/* Foundation components */'));
|
|
$block = substr($block, 0, (int) strpos($block, '/* Actions and communication */'));
|
|
} else {
|
|
$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');
|