Plan step 39 (parts 1-3, folded into one commit: PHP tests read the deleted files directly, so they cannot land apart from it). Tailwind leaves the whole stack: - Delete resources/css/tailwind.css, tokens/theme.css and tokens/utilities.css. Neither token file declared an --md-sys-* custom property of its own (both only referenced tokens declared elsewhere), so nothing loses a value; the md-* interaction and text classes already mirror utilities.css's declarations exactly (foundation/interaction.css, text.css). - npm uninstall tailwindcss @tailwindcss/vite; vite.config.js drops the plugin and its import; composer.json drops the tailwindcss keyword (no lock change — keywords are outside Composer's content hash). - The Workbench now builds one CSS entry, workbench/resources/css/app.css (all.css, showcase.css and the scheme; package.css is folded in and removed) instead of two, used by ErrorPage::assets() and every browser-test probe page's raw @vite() call; the showcase's own pages still take their CSS from the bundle route. - TokensTest and StylesheetsTest: the two facts theme.css and utilities.css carried (every scheme role becomes a colour, resolved on the element; md-type-* matches the type-* utilities) are asserted directly against the scheme and text.css now that there is no second copy to cross-check; StylesheetsTest gained a full-tree scan (every .css file under resources/css/ is reached from all.css or showcase.css, no exclusions left for Tailwind); the Workbench-entry test and every "moved out of tailwind.css" assertion updated for the single entry and its removal. - DesignGuard.php's comments and the development skill's setup section no longer name the deleted files or a second Tailwind entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
103 lines
5.0 KiB
PHP
103 lines
5.0 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 all.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
|
|
* all.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 rendered tag that is not a components/ stylesheet at all (a layout component's, e.g.
|
|
// <x-pane>) has no import of its own to check here.
|
|
->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 all.css, or icon and shape from the foundation block before it', function (string $name) {
|
|
$block = allCssBlock(in_array($name, ['icon', 'shape'], true) ? 'Foundation components' : 'Actions and communication');
|
|
|
|
expect($block)->toContain("@import './components/{$name}.css';");
|
|
})->with('action components');
|