Scan for Tailwind breakpoints, and drive the shell by class in the browser
Plan steps 15-17's testing. `tests/Feature/BreakpointsTest.php` reads every file under resources/views, resources/css and resources/js — showcase example heredocs included — and fails on any Tailwind breakpoint prefix and on any media query or `matchMedia` string at 40, 48, 64 or 80rem (or 640/768/1024/ 1280px, or the 39.99rem that stood for "below 640"). Both scans were checked against a deliberately reintroduced `sm:w-auto` and an 80rem query. Every render test that asserted a `sm:`/`lg:`/`xl:` class now asserts the M3 class, the drawer's pane query is 52.5rem, and the theme script keeps `data-rail-auto` through `wire:navigate`. NavigationTest drives the shell at 599/600/839/840/1199/1200/1600 with the expectation each class carries: the bar on a compact window, the 96px rail from the medium class's first pixel, a standard rail collapsed through expanded whose menu button widens it in place with no scrim, and the rail expanded to begin with from large and at extra-large. (Not run here — the Browser suite is out of scope for this stream.) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
afc8e199ae
commit
9fb3126038
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* Breakpoints are M3's window size classes and only those: compact below `medium`, then medium 600,
|
||||
* expanded 840, large 1200 and extra-large 1600 (docs/reference/m3/foundations.md § Layout,
|
||||
* foundations-supplement.md § Breakpoints). Tailwind's own screens are cleared in
|
||||
* resources/css/tokens/theme.css — `--breakpoint-*: initial` — so an `sm:` left behind compiles to
|
||||
* nothing at all and the rule it carried silently disappears. These two scans are the guard: one
|
||||
* for the utilities, one for the media queries a stylesheet or a script writes by hand.
|
||||
*
|
||||
* @return list<SplFileInfo>
|
||||
*/
|
||||
function breakpointSources(): array
|
||||
{
|
||||
return collect(['views', 'css', 'js'])
|
||||
->flatMap(fn (string $directory): array => File::allFiles(__DIR__.'/../../resources/'.$directory))
|
||||
->filter(fn (SplFileInfo $file): bool => in_array($file->getExtension(), ['php', 'css', 'js'], true))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Every line of a file that matches, as `path:line: the line`.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
function breakpointOffenders(SplFileInfo $file, string $pattern): array
|
||||
{
|
||||
$lines = preg_split('/\R/', $file->getContents());
|
||||
|
||||
return collect($lines)
|
||||
->filter(fn (string $line): bool => preg_match($pattern, $line) === 1)
|
||||
->map(fn (string $line, int $index): string => $file->getRelativePathname().':'.($index + 1).': '.trim($line))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
it('writes no Tailwind breakpoint prefix in any view, stylesheet or script', function () {
|
||||
// A prefix, not a name: `--radius-corner-sm:` and slider.js's `sm: { corner: 8 }` are excluded
|
||||
// by the character before and the one after, and `@md:` is a container query, a different
|
||||
// thing — a component sized by the room it has, not by the window.
|
||||
$pattern = '/(?<![\w@-])(?:max-)?(?:sm|md|lg|xl|2xl):[a-z\[(-]/';
|
||||
|
||||
$offenders = collect(breakpointSources())
|
||||
->flatMap(fn (SplFileInfo $file): array => breakpointOffenders($file, $pattern))
|
||||
->values();
|
||||
|
||||
expect($offenders->all())->toBe([]);
|
||||
});
|
||||
|
||||
it('writes no media query at a Tailwind screen width', function () {
|
||||
// 40rem/640px, 48rem/768px, 64rem/1024px, 80rem/1280px and the 39.99rem that stood for "below
|
||||
// 640" — none of them is an M3 boundary. The M3 ones are 37.5, 52.5, 75 and 100rem, and a
|
||||
// script asks resources/js/breakpoints.js for them instead of writing its own string.
|
||||
$widths = '/(?<![\d.])(?:40|48|64|80|39\.99)rem|(?<![\d.])(?:640|768|1024|1280)px/';
|
||||
|
||||
$offenders = collect(breakpointSources())
|
||||
->flatMap(function (SplFileInfo $file) use ($widths): array {
|
||||
// Only the condition of a query, so `max-width: min(40rem, 70dvh)` — a real width, not
|
||||
// a breakpoint — and a 640px cap on a bottom sheet are left alone.
|
||||
preg_match_all('/@(?:media|container)\b[^{]*|matchMedia\(\s*[\'"`][^\'"`]*/', $file->getContents(), $matches);
|
||||
|
||||
return collect($matches[0])
|
||||
->filter(fn (string $condition): bool => preg_match($widths, $condition) === 1)
|
||||
->map(fn (string $condition): string => $file->getRelativePathname().': '.trim($condition))
|
||||
->values()
|
||||
->all();
|
||||
})
|
||||
->values();
|
||||
|
||||
expect($offenders->all())->toBe([]);
|
||||
});
|
||||
|
||||
it('scans the showcase example heredocs too, where the package teaches its own markup', function () {
|
||||
$sections = collect(breakpointSources())
|
||||
->filter(fn (SplFileInfo $file): bool => str_contains($file->getRelativePathname(), 'showcase'.DIRECTORY_SEPARATOR.'sections'))
|
||||
->filter(fn (SplFileInfo $file): bool => str_contains($file->getContents(), "<<<'BLADE'"));
|
||||
|
||||
// The scan reads whole files, so nothing above can be hiding inside a heredoc; this only holds
|
||||
// the premise up, in case the examples ever move out of them.
|
||||
expect($sections)->not->toBeEmpty()
|
||||
->and($sections->filter(fn (SplFileInfo $file): bool => str_contains($file->getContents(), 'medium:'))->isNotEmpty())->toBeTrue();
|
||||
});
|
||||
Reference in New Issue
Block a user