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
@@ -16,9 +16,11 @@ function navigationReady(mixed $page): mixed
|
||||
function shellPage(int $width = 1280, int $height = 900, string $path = '/material/shell'): mixed
|
||||
{
|
||||
// The window is resized after the page starts loading, and an adaptive rail hears of the new
|
||||
// width from a media query's change event, which can arrive after the first key press.
|
||||
// width from a media query's change event, which can arrive after the first key press. Both
|
||||
// of its queries are M3 window size classes: `expanded` (840px) says the rail is standard
|
||||
// rather than modal, `large` (1200px) that it starts expanded rather than collapsed.
|
||||
return navigationReady(visit($path)->resize($width, $height))
|
||||
->assertScript("window.eval(\"(() => { const rail = document.querySelector('[data-navigation-rail]'); return ! rail || rail.dataset.navigationRail !== 'adaptive' || Alpine.\$data(rail).wide === window.matchMedia('(min-width: 64rem)').matches; })()\")");
|
||||
->assertScript("window.eval(\"(() => { const rail = document.querySelector('[data-navigation-rail]'); if (! rail || rail.dataset.navigationRail !== 'adaptive') return true; const state = Alpine.\$data(rail); return state.wide === window.matchMedia('(width >= 840px)').matches && state.roomy === window.matchMedia('(width >= 1200px)').matches; })()\")");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,8 +63,8 @@ function railWidth(int $pixels): string
|
||||
return 'Math.round('.RAIL.".getBoundingClientRect().width) === {$pixels}";
|
||||
}
|
||||
|
||||
it('shows the navigation bar on a phone and marks the current destination', function () {
|
||||
shellPage(400, 860)
|
||||
it('shows the navigation bar on a compact window and marks the current destination', function () {
|
||||
shellPage(599, 860)
|
||||
->assertScript('getComputedStyle('.BOTTOM_BAR.").display !== 'none'")
|
||||
->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'")
|
||||
->assertScript(BOTTOM_BAR.".querySelectorAll('[data-navigation-bar-item]').length === 4")
|
||||
@@ -72,8 +74,13 @@ it('shows the navigation bar on a phone and marks the current destination', func
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
it('collapses the rail on a medium window, icon over label', function () {
|
||||
shellPage(800)
|
||||
it('collapses the rail across the medium class, icon over label, from its first pixel', function () {
|
||||
shellPage(600)
|
||||
->assertScript(railWidth(96))
|
||||
->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'")
|
||||
->assertNoJavaScriptErrors();
|
||||
|
||||
shellPage(839)
|
||||
->assertScript(railWidth(96))
|
||||
->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'")
|
||||
->assertScript("getComputedStyle(document.querySelector('[data-navigation-rail-item]')).flexDirection === 'column'")
|
||||
@@ -81,10 +88,42 @@ it('collapses the rail on a medium window, icon over label', function () {
|
||||
->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('expands the rail from lg, and keeps it collapsed across a reload from the first paint', function () {
|
||||
it('starts the rail collapsed across the expanded class and expands it in the layout', function () {
|
||||
// 840-1199 is M3's expanded class: a standard rail, collapsed until someone says otherwise,
|
||||
// whose menu button widens it in place — no scrim, no focus trap, the page beside it live.
|
||||
shellPage(1199)
|
||||
->assertScript(railWidth(96))
|
||||
->assertScript("document.documentElement.hasAttribute('data-rail-auto')")
|
||||
->assertScript("localStorage.getItem('material-rail') === null")
|
||||
->assertNoJavaScriptErrors();
|
||||
|
||||
$page = shellPage(840)
|
||||
->assertScript(railWidth(96))
|
||||
->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'")
|
||||
->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'false');
|
||||
|
||||
$page->click('[data-navigation-rail-menu]')
|
||||
->assertScript(railWidth(256))
|
||||
->assertScript('! '.RAIL.".hasAttribute('data-open')")
|
||||
->assertScript("getComputedStyle(document.querySelector('[data-navigation-rail-scrim]')).display === 'none'")
|
||||
->assertScript("document.getElementById('content').closest('[aria-hidden=\"true\"]') === null")
|
||||
->assertScript("document.documentElement.getAttribute('data-rail') === 'expanded'")
|
||||
->assertScript("! document.documentElement.hasAttribute('data-rail-auto')")
|
||||
->assertScript("localStorage.getItem('material-rail') === 'expanded'")
|
||||
->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'true')
|
||||
->assertNoJavaScriptErrors();
|
||||
|
||||
// The choice outlives the reload, and holds to the top of the class.
|
||||
navigationReady($page->refresh())->assertScript(railWidth(256));
|
||||
});
|
||||
|
||||
it('expands the rail from large, and keeps it collapsed across a reload from the first paint', function () {
|
||||
firstPaintProbe();
|
||||
|
||||
$page = shellPage(1280, 900, '/rail-probe')
|
||||
// 1200 is the first pixel of M3's large class, where the rail starts expanded. The first-paint
|
||||
// probe runs before the resize, so the harness's own window must be in the same class — it is:
|
||||
// Playwright opens 1280 wide.
|
||||
$page = shellPage(1200, 900, '/rail-probe')
|
||||
->assertScript(railWidth(256))
|
||||
->assertScript("window.eval('window.firstPaint.width') === '256px'")
|
||||
->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'true');
|
||||
@@ -100,10 +139,21 @@ it('expands the rail from lg, and keeps it collapsed across a reload from the fi
|
||||
->assertScript("window.eval('window.firstPaint.rail') === 'collapsed'")
|
||||
->assertScript("window.eval('window.firstPaint.width') === '96px'")
|
||||
->assertScript(railWidth(96));
|
||||
|
||||
// Extra-large is the same band, and the collapse just chosen still holds there.
|
||||
shellPage(1600, 900)->assertScript(railWidth(96));
|
||||
});
|
||||
|
||||
it('opens the modal rail from its menu button below lg, holding focus until Escape or the scrim', function () {
|
||||
$page = shellPage(800);
|
||||
it('expands the rail by default at extra-large', function () {
|
||||
shellPage(1600, 900)
|
||||
->assertScript(railWidth(256))
|
||||
->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'")
|
||||
->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'true')
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
it('opens the modal rail from its menu button below expanded, holding focus until Escape or the scrim', function () {
|
||||
$page = shellPage(839);
|
||||
|
||||
// From the keyboard: WebKit neither focuses a button on click nor returns focus to one that never had it.
|
||||
$page->script("document.querySelector('[data-navigation-rail-menu]').focus()");
|
||||
@@ -129,8 +179,8 @@ it('opens the modal rail from its menu button below lg, holding focus until Esca
|
||||
->assertScript("localStorage.getItem('material-rail') === null");
|
||||
});
|
||||
|
||||
it('slides the modal rail in on a phone from the app bar\'s menu button', function () {
|
||||
$page = shellPage(400, 860);
|
||||
it('slides the modal rail in on a compact window from the app bar\'s menu button', function () {
|
||||
$page = shellPage(599, 860);
|
||||
|
||||
$page->click('@shell-menu')
|
||||
->assertScript(RAIL.".hasAttribute('data-open')")
|
||||
@@ -144,7 +194,7 @@ it('slides the modal rail in on a phone from the app bar\'s menu button', functi
|
||||
});
|
||||
|
||||
it('keeps the rail and the theme through wire:navigate and moves aria-current', function () {
|
||||
$page = shellPage(1280);
|
||||
$page = shellPage(1200);
|
||||
|
||||
$page->click('[data-navigation-rail-menu]')
|
||||
->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'");
|
||||
@@ -193,7 +243,7 @@ it('lifts the snackbar and the page above something docked on the phone\'s bar',
|
||||
$snackbarBottom = "Math.round(parseFloat(getComputedStyle(document.querySelector('[x-data=\"materialSnackbar\"]')).bottom))";
|
||||
$contentPadding = "Math.round(parseFloat(getComputedStyle(document.getElementById('content')).paddingBottom))";
|
||||
|
||||
$page = shellPage(400, 860);
|
||||
$page = shellPage(599, 860);
|
||||
$snackbar = (int) $page->script($snackbarBottom);
|
||||
$content = (int) $page->script($contentPadding);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user