Count a row written by hand in the design guard's stylesheet checks

`data-md-list-row` on an application's own `<li>`, `<div>`, `<tr>` or
`<x-row>` draws its hover, focus and press state layer, its cursor and
its `data-md-selected` fill from list-item.css, but missingStylesheets()
and unusedStylesheets() read only component tags and `->links()`.
ReStride had the stylesheet only because table.css imports it: had its
last `<x-table>` gone, every hand-made row would have lost its states
and its fill without a finding, and importing list-item.css for the
rows directly was reported unused.

A Blade view outside the package's own `resources/views` that writes a
hook in the new `HOOK_STYLESHEETS` now needs that hook's stylesheet in
both checks, named at the earliest line that needs it. The one entry is
`data-md-list-row` → `components/list-item.css`, except on `<x-card>`
(any spelling), whose row card.css draws; the card's opening tag is
blanked out before the hook is read, and a selector such as
`[data-md-list-row]` or a name in backticks writes no row. Nothing else
list-rows.js, the skill or the README has an application write needs a
stylesheet of its own: `data-md-list-open` and a row's
`data-md-selected` are drawn only through the row, a selected row in
`<x-table>` by table.css, `data-md-dragged` by foundation.css's
`md-state-layer` or card.css, `data-md-field-control` by field.css
inside `<x-field>`. The constant's docblock says so, and the guard's
docblocks, the README, the skill and UPGRADE.md describe the check.

A fixture test reads a hand-made `<li>` and `<tr>` row and a card-only
view (plain and namespaced `<x-card data-md-list-row>`, and a selector
in a script) against both checks; it fails without the change. A
second test scans the package's list-item and card views and expects
no row finding; it fails when package views are read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 08:05:33 +02:00
co-authored by Claude Opus 5
parent b62c83f244
commit 471d927e64
8 changed files with 208 additions and 47 deletions
+33
View File
@@ -583,6 +583,39 @@ it('needs pagination.css when a view calls ->links()', function () {
]);
});
it('needs list-item.css for a row written by hand, and leaves a card row to card.css', function () {
// rows.blade.php writes `data-md-list-row` on an <li> and a <tr> and renders no package tag;
// card-rows.blade.php writes it only on <x-card>, whose row card.css draws, and in a selector.
$rows = realpath(GUARD_FIXTURES.'/stylesheets/views/rows.blade.php');
$cardRows = realpath(GUARD_FIXTURES.'/stylesheets/views/card-rows.blade.php');
$foundationOnly = realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css');
$entry = realpath(GUARD_FIXTURES.'/stylesheets/rows.css');
expect(fixtureRelative(DesignGuard::scan($rows)->missingStylesheets($foundationOnly)->violations()))->toBe([
"stylesheets/views/rows.blade.php:3 `data-md-list-row` needs `components/list-item.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/list-item.css';`",
])
->and(fixtureRelative(DesignGuard::scan($rows)->unusedStylesheets($entry)->violations()))->toBe([
"stylesheets/rows.css:2 `components/card.css` is imported, but no scanned view renders a component that needs it — remove `@import '../../../../resources/css/components/card.css';`",
])
->and(fixtureRelative(DesignGuard::scan($cardRows)->missingStylesheets($foundationOnly)->violations()))->toBe([
"stylesheets/views/card-rows.blade.php:1 `<x-card>` needs `components/card.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/card.css';`",
])
->and(fixtureRelative(DesignGuard::scan($cardRows)->unusedStylesheets($entry)->violations()))->toBe([
"stylesheets/rows.css:3 `components/list-item.css` is imported, but no scanned view renders a component that needs it — remove `@import '../../../../resources/css/components/list-item.css';`",
]);
});
it('reads a row written by hand in the application\'s views only, not in the package\'s own', function () {
// list-item.blade.php and card.blade.php write `data-md-list-row` for the tags that render them.
$components = realpath(__DIR__.'/../../resources/views/components');
$violations = DesignGuard::scan([$components.'/list-item.blade.php', $components.'/card.blade.php'])
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css'))
->violations();
expect(array_values(array_filter($violations, fn (string $violation): bool => str_contains($violation, '`data-md-list-row`'))))->toBe([]);
});
it('reports a package tag the application shadows with its own anonymous component', function () {
$views = sys_get_temp_dir().'/livewire-material-guard-shadow-'.uniqid();
File::ensureDirectoryExists($views.'/components');
@@ -0,0 +1,3 @@
@import '../../../../resources/css/foundation.css';
@import '../../../../resources/css/components/card.css';
@import '../../../../resources/css/components/list-item.css';
@@ -0,0 +1,9 @@
<x-card data-md-list-row>
<a href="#" data-md-list-open>Report</a>
</x-card>
<x-livewire-material::card wire:key="report-{{ $report->id }}" :data-md-list-row="$report->opens()">
<a href="#" data-md-list-open>Report</a>
</x-livewire-material::card>
<script>
document.querySelectorAll('[data-md-list-row]').forEach((row) => row.focus())
</script>
@@ -0,0 +1,11 @@
{{-- A row written by hand: `data-md-list-row` in a comment writes none. --}}
<ul>
<li data-md-list-row @if ($open) data-md-selected @endif>
<button type="button" wire:click="open" data-md-list-open>Report</button>
</li>
</ul>
<table>
<tr data-md-list-row data-md-selected>
<td><a href="#" data-md-list-open>Report</a></td>
</tr>
</table>