Files
livewire-material/tests/Feature/Components/LayoutComponentsTest.php
T
Andreas Reinhold / reiniandClaude Opus 5 247c596c3a Cut duplicated and speculative code across the package
An over-engineering audit of the whole tree, applied in five reviewed
batches. Behaviour stays the same except where UPGRADE.md says otherwise.

PHP: the showcase and error-page stylesheets are prebuilt into
resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import
(first occurrence kept, the order an application's build gives), instead
of Stylesheets::bundle() inlining imports on every request; only the
import walk DesignGuard needs stays. SchemeStylesheet::withProfiles()
replaces three copies of the scheme-plus-profiles loop, material:scheme
leaves spec and contrast checks to the node script that already made
them, and the error page's scheme cache, the hashed view namespace, the
translations path with no lang/ folder and DesignGuard's 1.x-name hints
are gone.

JS: the androidx shape port progress.js and both bin scripts each carried
lives once in resources/js/shapes.js (the generated SVGs are unchanged);
util.js holds ringIndex(), ms(), reopenGuard() and remember(), which
were written out several times; listeners are released through
AbortController; tooltip.js's hoverPopover() serves the rich tooltip too.

CSS: every rule for an element inside the navigation rail queries
`--md-navigation-rail-value` instead of repeating the seven collapsed
conditions under five media branches; badge, alert, progress, slider and
button read one non-inheriting colour-role table (components/color.css);
the dialog chrome, the submenu's popover chrome, the chip's state layer
and touch target, and the visually-hidden inputs use the shared rules
they copied; foundation/tokens.css is folded into foundation.css.

Views: Support\Field and Support\Link replace the error-key, bound-value
and link-attribute blocks copied into the fields and link components;
the timepicker period group, the menu filter and the showcase head are
partials; the datepicker's steppers and entry fields are loops; component
docblocks no longer restate SKILL.md.

Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces
four per-group files, DesignGuardTest and the layout-component tests use
datasets, browser tests share one ready() helper, CSS parsing lives in
ComponentStylesheet alone. docs/audits and the finding IDs citing it are
removed, as are pestphp/pest-plugin-laravel, the unused composer scripts
and check:font; the lint job runs in the feature job, which now installs
node packages so the prebuilt-stylesheet staleness test runs in CI.

Feature suite 1177 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:29:21 +02:00

69 lines
3.8 KiB
PHP

<?php
/**
* Two checks every layout component repeated once per file: the root passes through `as`, a
* visibility prop and the caller's own `class`/`style` (after its own style, where it writes one)
* and any other attribute; the four with a `gap` prop replace it with a spacing token or drop to
* `data-md-gap="none"` for anything else. Folded into one dataset each here, keyed by tag, rather
* than kept once per component's own test file — <x-grid>'s style differs (a long per-breakpoint
* column list before the caller's own, checked with `$styleEndsWith` instead of the exact string
* the others match) and <x-stack>'s gap check has an extra invalid case (`space1000`, a token name
* past the scale); both are still one dataset row, not a reason to keep the test itself apart.
*/
dataset('layout component root attributes', [
'feed' => [
'<x-feed as="ul" hide-below="medium" class="photos" style="padding-block: 1rem" />',
['<' => 'ul', 'data-md-hide-below' => 'medium', 'class' => 'photos', 'style' => '--md-min-item: 240px; padding-block: 1rem;'],
],
'grid' => [
'<x-grid as="ul" hide-below="expanded" class="gallery" style="align-items: start" />',
['<' => 'ul', 'data-md-hide-below' => 'expanded', 'class' => 'gallery'],
'--md-columns-extra-large: 1; align-items: start;',
],
'row' => [
'<x-row as="nav" hide-from="expanded" class="toolbar-row" style="min-height: 3rem" aria-label="Filters" />',
['<' => 'nav', 'data-md-hide-from' => 'expanded', 'class' => 'toolbar-row', 'style' => 'min-height: 3rem;', 'aria-label' => 'Filters'],
],
'stack' => [
'<x-stack as="ul" hide-below="medium" hide-from="large" class="steps" style="max-width: 40rem" />',
['<' => 'ul', 'data-md-hide-below' => 'medium', 'data-md-hide-from' => 'large', 'class' => 'steps', 'style' => 'max-width: 40rem;'],
],
'pane' => [
'<x-pane as="section" hide-below="expanded" class="checkout" style="max-width: 50rem" aria-labelledby="checkout-title" />',
['<' => 'section', 'data-md-hide-below' => 'expanded', 'class' => 'checkout', 'style' => 'max-width: 50rem;', 'aria-labelledby' => 'checkout-title'],
],
'list-detail' => [
'<x-list-detail as="section" hide-below="medium" class="inbox" style="min-height: 30rem" x-model="chosen" />',
['<' => 'section', 'data-md-hide-below' => 'medium', 'class' => 'inbox', 'style' => 'min-height: 30rem;', 'x-model' => 'chosen', 'x-modelable' => 'selected'],
],
'supporting-pane' => [
'<x-supporting-pane as="main" hide-from="extra-large" class="editor" style="min-height: 20rem" />',
['<' => 'main', 'data-md-hide-from' => 'extra-large', 'class' => 'editor', 'style' => 'min-height: 20rem;'],
],
]);
it('takes the element, the visibility props, and the caller\'s class and style after its own', function (string $blade, array $expected, ?string $styleEndsWith = null) {
$root = layoutRoot((string) $this->blade($blade));
expect($root)->toMatchArray($expected);
if ($styleEndsWith !== null) {
expect($root['style'])->toEndWith($styleEndsWith);
}
})->with('layout component root attributes');
dataset('layout component gaps', [
'feed' => ['feed', 'space400', ['wide']],
'grid' => ['grid', 'space300', ['1.5rem']],
'row' => ['row', 'space100', ['tight']],
'stack' => ['stack', 'space200', ['16px', 'space1000']],
]);
it('replaces the gap prop with a spacing token, and with none for anything else', function (string $tag, string $token, array $invalid) {
expect(layoutRoot((string) $this->blade("<x-{$tag} gap=\"{$token}\" />"))['data-md-gap'])->toBe($token);
foreach ($invalid as $bad) {
expect(layoutRoot((string) $this->blade("<x-{$tag} gap=\"{$bad}\" />"))['data-md-gap'])->toBe('none');
}
})->with('layout component gaps');