Add fixtures and tests for DesignGuard::missingStylesheets()

Plan step 41(ii): exercises the tag-to-stylesheet mapping (plain, prefixed and
namespaced spellings), a package stylesheet's own imports counting toward its
dependents (split-button.css satisfies button.css and menu.css too),
->links() needing pagination.css, the always-required foundation.css, and a
package tag shadowed by the application's own anonymous or class component.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 08:12:01 +02:00
co-authored by Claude Sonnet 5
parent 08f2ae9d76
commit b998683762
13 changed files with 121 additions and 0 deletions
+106
View File
@@ -1,9 +1,14 @@
<?php
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\View;
use NoNameWeb\LivewireMaterial\Support\Stylesheets;
use NoNameWeb\LivewireMaterial\Testing\DesignGuard;
const GUARD_FIXTURES = __DIR__.'/../Fixtures/design-guard';
beforeEach(fn () => Stylesheets::resetCache());
/**
* @param list<string> $violations
* @return list<string>
@@ -305,6 +310,107 @@ it('bans any further pattern', function () {
expect($violations)->toContain('views/page.blade.php:7 a retired utility');
});
it('names the missing package stylesheet and the exact @import line to add', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/split-button.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css'))
->violations());
expect($violations)->toBe([
"stylesheets/views/split-button.blade.php:1 `<x-split-button>` needs `components/split-button.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/split-button.css';`",
]);
});
it('counts what an imported package stylesheet already brings in, through its own imports', function () {
$violations = DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/dependencies.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/split-button.css'))
->violations();
expect($violations)->toBe([]);
});
it('needs nothing more once the entry imports all.css', function () {
$violations = DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/all.css'))
->violations();
expect($violations)->toBe([]);
});
it('requires foundation.css whatever the views hold', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/plain.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/missing-foundation.css'))
->violations());
expect($violations)->toBe([
"stylesheets/missing-foundation.css:1 the CSS entry never imports `foundation.css`, required by every package stylesheet — add `@import '../../../../resources/css/foundation.css';`",
]);
});
it('recognises a package tag written under the configured prefix', function () {
config(['livewire-material.prefix' => 'm']);
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/prefixed.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css'))
->violations());
expect($violations)->toBe([
"stylesheets/views/prefixed.blade.php:1 `<x-button>` needs `components/button.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/button.css';`",
]);
});
it('recognises a package tag written fully qualified', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/namespaced.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css'))
->violations());
expect($violations)->toBe([
"stylesheets/views/namespaced.blade.php:1 `<x-button>` needs `components/button.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/button.css';`",
]);
});
it('needs pagination.css when a view calls ->links()', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/pagination.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/foundation-only.css'))
->violations());
expect($violations)->toBe([
"stylesheets/views/pagination.blade.php:1 `->links()` needs `components/pagination.css`, missing from stylesheets/foundation-only.css — add `@import '../../../../resources/css/components/pagination.css';`",
]);
});
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');
File::put($views.'/components/button.blade.php', '<span>the application\'s button</span>');
View::getFinder()->prependLocation($views);
try {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/shadow.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/all.css'))
->violations());
} finally {
File::deleteDirectory($views);
}
expect($violations)->toBe([
"stylesheets/views/shadow.blade.php:1 `<x-button>` is shadowed by the application's own component of the same name — the package's `<x-button>` never renders here",
]);
});
it('reports a package tag the application shadows with its own component class', function () {
if (! class_exists('App\View\Components\Card', false)) {
eval('namespace App\View\Components; class Card {}');
}
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/shadow-class.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/all.css'))
->violations());
expect($violations)->toBe([
"stylesheets/views/shadow-class.blade.php:1 `<x-card>` is shadowed by the application's own component of the same name — the package's `<x-card>` never renders here",
]);
});
it('passes the package\'s own views', function () {
$violations = DesignGuard::scan([__DIR__.'/../../resources/views', __DIR__.'/../../resources/js', __DIR__.'/../../src'])->violations();
@@ -0,0 +1 @@
@import '../../../../resources/css/all.css';
@@ -0,0 +1 @@
@import '../../../../resources/css/foundation.css';
@@ -0,0 +1 @@
@import '../../../../resources/css/components/button.css';
@@ -0,0 +1,2 @@
@import '../../../../resources/css/foundation.css';
@import '../../../../resources/css/components/split-button.css';
@@ -0,0 +1,3 @@
<x-button label="Save" />
<x-menu></x-menu>
<x-split-button label="Save" />
@@ -0,0 +1 @@
<x-livewire-material::button label="Save" />
@@ -0,0 +1 @@
{{ $items->links() }}
@@ -0,0 +1 @@
<div>Nothing package-specific here.</div>
@@ -0,0 +1 @@
<x-m::button label="Save" />
@@ -0,0 +1 @@
<x-card title="Report"></x-card>
@@ -0,0 +1 @@
<x-button label="Save" />
@@ -0,0 +1 @@
<x-split-button label="Save" />