tests / feature (8.4) (push) Successful in 2m0s
tests / feature (8.5) (push) Successful in 2m0s
tests / browser (chrome, chromium) (push) Failing after 8m3s
tests / browser (firefox, firefox) (push) Failing after 12m58s
tests / browser (safari, webkit) (push) Failing after 13m8s
Tailwind left the stack in 2.0.0, but the package still carried about 330 mentions of it. What the guard's Tailwind detection protected — a class that compiles to nothing — is now protected by a check that does not care where a dead class came from. DesignGuard: about 500 lines of Tailwind tables, scales, palettes and "2.0.0 replacement" hints give way to one check — a class a view or PHP file writes that neither the application's stylesheets nor the package's own declare. It catches a utility of any framework, a typo and a class whose rules were deleted alike, so it also found two classes ReStride draws nothing with. A stylesheet has to be in reach for it: the `.css` files among the scanned paths, or what the `missingStylesheets()` entry imports. The class reader no longer mistakes an array index for a class list (`$block['base']`), and it reads the array a class helper is given, where it read nothing before. The package's own three Tailwind self-guards go with it. Only their one unique check stays, as a test of its own: every `matchMedia` width in resources/js is an M3 breakpoint. The pagination views are `material.blade.php` and `simple-material.blade.php`; only Laravel's and Livewire's default theme names ever made them `tailwind`. The provider sets `Paginator`'s default views and switches `livewire.pagination_theme` to `material` when it is still Livewire's own default, so no application can forget the config; a theme an application chose, and a component's own `$paginationTheme` or `paginationView()`, still win. The rest is prose: the layer-order guidance for an application that still builds Tailwind, the Tailwind wording in the README, the Boost guidelines and the development skill, and about 25 "this used to be a Tailwind utility" comments, along with every "plan step NN" pointer into a gitignored folder. The reset keeps its credit, and NOTICE now carries it too. Feature suite 1159 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
3.4 KiB
PHP
85 lines
3.4 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use NoNameWeb\LivewireMaterial\Tests\TestCase;
|
|
|
|
pest()->extend(TestCase::class)->in('Feature', 'Browser');
|
|
|
|
/**
|
|
* The attributes of a rendered component's tag, by name; a boolean attribute Blade renders as
|
|
* `name="name"`. Without `$tag`, it is the first tag anywhere in the rendered Blade (anchored to
|
|
* the start), and the element name comes back too, under `<` — the layout components' render
|
|
* tests, which assert what their root carries, read it this way. With `$tag` (`|`-separated for
|
|
* more than one, e.g. `'button|a'`), it is the first `<$tag>` anywhere in the rendered Blade
|
|
* instead, with no `<` key — for a component whose root is not the tag Blade renders first (a
|
|
* button or link inside a wrapper, a span), where the anchored match would read the wrong element.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
function layoutRoot(string $html, ?string $tag = null): array
|
|
{
|
|
if ($tag === null) {
|
|
preg_match('/^\s*<([a-z]+)\b([^>]*)>/s', $html, $match);
|
|
$root = ['<' => $match[1] ?? ''];
|
|
$attributes = $match[2] ?? '';
|
|
} else {
|
|
preg_match('/<(?:'.$tag.')\b([^>]*)>/', $html, $match);
|
|
$root = [];
|
|
$attributes = $match[1] ?? '';
|
|
}
|
|
|
|
preg_match_all('/([\w:.@-]+)(?:="([^"]*)")?/', $attributes, $pairs, PREG_SET_ORDER);
|
|
|
|
return $root + collect($pairs)->mapWithKeys(fn (array $pair): array => [$pair[1] => $pair[2] ?? ''])->all();
|
|
}
|
|
|
|
/**
|
|
* The page is done loading, and Alpine and/or Livewire (whichever the page renders) have booted —
|
|
* chained after visit(), before a Browser test probe reads anything either one wires up. Most
|
|
* pages need both; one that renders no Livewire component needs Alpine alone, and vice versa.
|
|
*/
|
|
function ready(mixed $page, bool $alpine = true, bool $livewire = true): mixed
|
|
{
|
|
$checks = ["document.readyState === 'complete'"];
|
|
|
|
if ($alpine) {
|
|
$checks[] = "typeof window.Alpine !== 'undefined'";
|
|
}
|
|
|
|
if ($livewire) {
|
|
$checks[] = "typeof window.Livewire !== 'undefined'";
|
|
}
|
|
|
|
return $page->waitForEvent('networkidle')->assertScript(implode(' && ', $checks));
|
|
}
|
|
|
|
// A shared CI runner is slower than a workstation: an animation or a smooth scroll can take longer
|
|
// than the default five seconds to settle there. BROWSER_TIMEOUT (milliseconds) raises the limit.
|
|
if (($timeout = (int) getenv('BROWSER_TIMEOUT')) > 0) {
|
|
pest()->browser()->timeout($timeout);
|
|
}
|
|
|
|
/**
|
|
* The named block of resources/css/all.css — the content between its `/* Heading *\/` comment and
|
|
* the next one, or the end of the file for the last block ("Navigation"). components.css and
|
|
* layout.css grouped their imports the same way before all.css folded both together; this
|
|
* is the one place that block lookup lives now, so a stylesheet test asks "is this imported from
|
|
* the Containment block" without repeating the search in every file that needs it. It is here,
|
|
* rather than in tests/Feature/StylesheetsTest.php, because Pest.php is always loaded, whichever
|
|
* test file or path is run.
|
|
*/
|
|
function allCssBlock(string $heading): string
|
|
{
|
|
$all = File::get(__DIR__.'/../resources/css/all.css');
|
|
$marker = "/* {$heading} */";
|
|
$start = strpos($all, $marker);
|
|
|
|
if ($start === false) {
|
|
throw new RuntimeException("all.css has no `{$marker}` block.");
|
|
}
|
|
|
|
$next = strpos($all, '/*', $start + strlen($marker));
|
|
|
|
return $next === false ? substr($all, $start) : substr($all, $start, $next - $start);
|
|
}
|