waitForEvent('networkidle') ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'"); } 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. 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; })()\")"); } /** * A shell whose page records, while the document is still being parsed — after the rail's * markup and the stylesheet, before Alpine exists — what and the rail looked like. * That is what the first paint shows. */ function firstPaintProbe(): void { Route::middleware('web')->get('/rail-probe', fn () => Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles

The page

@livewireScripts BLADE)); } 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) ->assertScript('getComputedStyle('.BOTTOM_BAR.").display !== 'none'") ->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'") ->assertScript(BOTTOM_BAR.".querySelectorAll('[data-navigation-bar-item]').length === 4") ->assertScript(BOTTOM_BAR.".querySelector('[aria-current=\"page\"]').textContent.includes('Inbox')") ->assertScript('getComputedStyle('.BOTTOM_BAR.".querySelector('[aria-current=\"page\"] [data-navigation-indicator]')).backgroundImage !== 'none'") ->assertScript('getComputedStyle('.BOTTOM_BAR.".querySelector('[data-navigation-bar-item]:not([aria-current]) [data-navigation-indicator]')).backgroundImage === 'none'") ->assertNoJavaScriptErrors(); }); it('collapses the rail on a medium window, icon over label', function () { shellPage(800) ->assertScript(railWidth(96)) ->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'") ->assertScript("getComputedStyle(document.querySelector('[data-navigation-rail-item]')).flexDirection === 'column'") ->assertScript("getComputedStyle(document.querySelector('[data-navigation-rail-heading]')).display === 'none'") ->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 () { firstPaintProbe(); $page = shellPage(1280, 900, '/rail-probe') ->assertScript(railWidth(256)) ->assertScript("window.eval('window.firstPaint.width') === '256px'") ->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'true'); $page->click('[data-navigation-rail-menu]') ->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'") ->assertScript("localStorage.getItem('material-rail') === 'collapsed'") ->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'false') ->assertScript(railWidth(96)); navigationReady($page->refresh()) ->assertScript("window.eval('window.firstPaint.alpine') === 'undefined'") ->assertScript("window.eval('window.firstPaint.rail') === 'collapsed'") ->assertScript("window.eval('window.firstPaint.width') === '96px'") ->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); // 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()"); $page->keys('[data-navigation-rail-menu]', 'Enter') ->assertScript(RAIL.".hasAttribute('data-open')") ->assertScript('Math.round('.RAIL_PANEL.'.getBoundingClientRect().width) === 256') ->assertScript(railWidth(96)) ->assertScript("getComputedStyle(document.querySelector('[data-navigation-rail-heading]')).display !== 'none'") ->assertScript(RAIL_PANEL.'.contains(document.activeElement)') ->assertScript("document.getElementById('content').closest('[aria-hidden=\"true\"]') !== null") ->assertAttribute('[data-navigation-rail-menu]', 'aria-expanded', 'true'); $page->keys(':focus', 'Escape') ->assertScript('! '.RAIL.".hasAttribute('data-open')") ->assertScript("document.getElementById('content').closest('[aria-hidden=\"true\"]') === null") ->assertScript("document.activeElement === document.querySelector('[data-navigation-rail-menu]')"); $page->keys('[data-navigation-rail-menu]', 'Enter') ->assertScript(RAIL.".hasAttribute('data-open')"); $page->click('[data-navigation-rail-scrim]') ->assertScript('! '.RAIL.".hasAttribute('data-open')") ->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); $page->click('@shell-menu') ->assertScript(RAIL.".hasAttribute('data-open')") ->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'flex'") ->assertScript('Math.round('.RAIL_PANEL.'.getBoundingClientRect().left) === 0') ->assertScript(RAIL_PANEL.'.contains(document.activeElement)'); $page->keys(':focus', 'Escape') ->assertScript('! '.RAIL.".hasAttribute('data-open')") ->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'"); }); it('keeps the rail and the theme through wire:navigate and moves aria-current', function () { $page = shellPage(1280); $page->click('[data-navigation-rail-menu]') ->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'"); $page->script("window.eval(\"Alpine.store('theme').set('dark'); window.samePage = true\")"); $page->click('[data-navigation-rail-panel] a[href$="/material/shell/starred"]') ->assertSeeIn('@shell-page', 'This is the starred page.') ->assertScript("window.eval('window.samePage') === true") ->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'") ->assertScript("document.documentElement.getAttribute('data-theme') === 'dark'") ->assertScript(railWidth(96)) ->assertScript("document.querySelector('[data-navigation-rail-panel] [aria-current=\"page\"]').getAttribute('href').endsWith('/material/shell/starred')") ->assertScript("document.querySelectorAll('[data-navigation-rail-panel] [aria-current=\"page\"]').length === 1") ->assertNoJavaScriptErrors(); }); it('skips to the content', function () { $page = shellPage(); $page->assertScript("document.querySelector('a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])').matches('[data-skip-link]')"); // Safari and WebKit leave links out of the Tab order, so focus it directly. $page->script("document.querySelector('[data-skip-link]').focus()"); $page->assertScript("getComputedStyle(document.querySelector('[data-skip-link]')).position === 'fixed'") ->assertVisible('[data-skip-link]'); $page->keys('[data-skip-link]', 'Enter') ->assertScript("location.hash === '#content'") ->assertScript("document.activeElement === document.getElementById('content')"); });