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. 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-md-navigation-rail]'); if (! rail || rail.dataset.mdNavigationRail !== 'adaptive') return true; const state = Alpine.\$data(rail); return state.wide === window.matchMedia('(width >= 840px)').matches && state.roomy === window.matchMedia('(width >= 1200px)').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 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-md-navigation-bar-item]').length === 4") ->assertScript(BOTTOM_BAR.".querySelector('[aria-current=\"page\"]').textContent.includes('Inbox')") ->assertScript('getComputedStyle('.BOTTOM_BAR.".querySelector('[aria-current=\"page\"] [data-md-navigation-indicator]')).backgroundImage !== 'none'") ->assertScript('getComputedStyle('.BOTTOM_BAR.".querySelector('[data-md-navigation-bar-item]:not([aria-current]) [data-md-navigation-indicator]')).backgroundImage === 'none'") ->assertNoJavaScriptErrors(); }); 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-md-navigation-rail-item]')).flexDirection === 'column'") ->assertScript("getComputedStyle(document.querySelector('[data-md-navigation-rail-heading]')).display === 'none'") ->assertAttribute('[data-md-navigation-rail-menu]', 'aria-expanded', 'false'); }); 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-md-navigation-rail-menu]', 'aria-expanded', 'false'); $page->click('[data-md-navigation-rail-menu]') ->assertScript(railWidth(256)) ->assertScript('! '.RAIL.".hasAttribute('data-md-open')") ->assertScript("getComputedStyle(document.querySelector('[data-md-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-md-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('morphs the rail header FAB between collapsed (icon only) and extended (with its label)', function () { $fab = "document.querySelector('[data-md-navigation-rail-fab-row] [data-md-fab]')"; $label = "document.querySelector('[data-md-navigation-rail-fab-row] [data-md-fab] > span')"; $page = shellPage(840) // Collapsed: the label is clipped to nothing, and the FAB is a plain square icon button — // navigation-rail.css's [data-md-fab] > span and [data-md-fab][data-md-extended] rules. ->assertScript("getComputedStyle({$label}).opacity === '0'") ->assertScript("getComputedStyle({$label}).maxWidth === '0px'") ->assertScript("Math.round({$fab}.getBoundingClientRect().width) === Math.round({$fab}.getBoundingClientRect().height)"); $page->click('[data-md-navigation-rail-menu]') // The label's width and the FAB's own shape both spring on the rail's spatial-default // transition; wait past it for the settled, extended end state. ->wait(0.6) ->assertScript("getComputedStyle({$label}).opacity === '1'") ->assertScript("parseFloat(getComputedStyle({$label}).maxWidth) > 0") ->assertScript("{$fab}.getBoundingClientRect().width > {$fab}.getBoundingClientRect().height") ->assertSee('Compose') ->assertNoJavaScriptErrors(); }); it('expands the rail from large, and keeps it collapsed across a reload from the first paint', function () { firstPaintProbe(); // 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-md-navigation-rail-menu]', 'aria-expanded', 'true'); $page->click('[data-md-navigation-rail-menu]') ->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'") ->assertScript("localStorage.getItem('material-rail') === 'collapsed'") ->assertAttribute('[data-md-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)); // Extra-large is the same band, and the collapse just chosen still holds there. The same page // widened: every visit() opens a fresh browser context, whose storage holds no choice. $page->resize(1600, 900) ->assertScript("window.matchMedia('(width >= 1600px)').matches") ->assertScript("document.documentElement.getAttribute('data-rail') === 'collapsed'") ->assertScript(railWidth(96)); }); it('expands the rail by default at extra-large', function () { shellPage(1600, 900) ->assertScript(railWidth(256)) ->assertScript('getComputedStyle('.BOTTOM_BAR.").display === 'none'") ->assertAttribute('[data-md-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-md-navigation-rail-menu]').focus()"); $page->keys('[data-md-navigation-rail-menu]', 'Enter') ->assertScript(RAIL.".hasAttribute('data-md-open')") ->assertScript('Math.round('.RAIL_PANEL.'.getBoundingClientRect().width) === 256') ->assertScript(railWidth(96)) ->assertScript("getComputedStyle(document.querySelector('[data-md-navigation-rail-heading]')).display !== 'none'") ->assertScript(RAIL_PANEL.'.contains(document.activeElement)') ->assertScript("document.getElementById('content').closest('[aria-hidden=\"true\"]') !== null") ->assertAttribute('[data-md-navigation-rail-menu]', 'aria-expanded', 'true'); $page->keys(':focus', 'Escape') ->assertScript('! '.RAIL.".hasAttribute('data-md-open')") ->assertScript("document.getElementById('content').closest('[aria-hidden=\"true\"]') === null") ->assertScript("document.activeElement === document.querySelector('[data-md-navigation-rail-menu]')"); $page->keys('[data-md-navigation-rail-menu]', 'Enter') ->assertScript(RAIL.".hasAttribute('data-md-open')"); $page->click('[data-md-navigation-rail-scrim]') ->assertScript('! '.RAIL.".hasAttribute('data-md-open')") ->assertScript("localStorage.getItem('material-rail') === null"); }); 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-md-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-md-open')") ->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'"); }); it('keeps the rail and the theme through wire:navigate and moves aria-current', function () { $page = shellPage(1200); $page->click('[data-md-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-md-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-md-navigation-rail-panel] [aria-current=\"page\"]').getAttribute('href').endsWith('/material/shell/starred')") ->assertScript("document.querySelectorAll('[data-md-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-md-skip-link]')"); // Safari and WebKit leave links out of the Tab order, so focus it directly. $page->script("document.querySelector('[data-md-skip-link]').focus()"); $page->assertScript("getComputedStyle(document.querySelector('[data-md-skip-link]')).position === 'fixed'") ->assertVisible('[data-md-skip-link]'); $page->keys('[data-md-skip-link]', 'Enter') ->assertScript("location.hash === '#content'") ->assertScript("document.activeElement === document.getElementById('content')"); }); it('moves an app bar\'s content under a safe area an application sets', function () { $row = "Math.round(document.querySelector('[data-md-app-bar] [data-md-app-bar-row]').getBoundingClientRect().top)"; $page = navigationReady(visit('/material')); $top = (int) $page->script($row); $page->script("document.documentElement.style.setProperty('--material-safe-top', '47px')"); $page->assertScript("{$row} === ".($top + 47)) ->assertNoJavaScriptErrors(); }); it('lifts the snackbar and the page above something docked on the phone\'s bar', function () { $snackbarBottom = "Math.round(parseFloat(getComputedStyle(document.querySelector('[x-data=\"materialSnackbar\"]')).bottom))"; $contentPadding = "Math.round(parseFloat(getComputedStyle(document.getElementById('content')).paddingBottom))"; $page = shellPage(599, 860); $snackbar = (int) $page->script($snackbarBottom); $content = (int) $page->script($contentPadding); expect($content)->toBe(64)->and($snackbar)->toBe(80); $page->script("document.documentElement.style.setProperty('--material-bottom-extra', '40px')"); $page->assertScript("{$snackbarBottom} === 120") ->assertScript("{$contentPadding} === 104"); }); /** * A page for the navigation bar and rail props the shell's own adaptive rail never exercises: * `tall`, `hide-on-scroll` (with a pinned snackbar), a narrow centred rail, and a rail that hides * when collapsed. A wide, fixed viewport throughout, so none of M3's breakpoints interfere with * what each example's own props already decide (docs/plans/material-3-browser-tests.md § Navigation). */ function navigationExtrasProbe(): mixed { Route::middleware('web')->get('/navigation-extras-probe', fn () => Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles
@livewireScripts BLADE)); return navigationReady(visit('/navigation-extras-probe')->resize(1000, 900)); } it('draws the tall navigation bar, icon over label at every width', function () { $page = navigationExtrasProbe(); $page->assertScript("Math.round(document.querySelector('#tall-bar [data-md-navigation-bar-items]').getBoundingClientRect().height) === 80") ->assertScript("getComputedStyle(document.querySelector('#tall-bar [data-md-navigation-pill]')).flexDirection === 'column'") ->assertNoJavaScriptErrors(); }); it('hides the bar on a scroll down and never while a pinned snackbar is on screen', function () { $page = navigationExtrasProbe(); $hidden = "document.querySelector('#hide-bar [data-md-navigation-bar]').hasAttribute('data-md-hidden')"; $page->script('window.scrollTo(0, 400)'); $page->assertScript("{$hidden} === true") ->assertNoJavaScriptErrors(); // Back to the top brings it back, as a fresh scroll-down would; pin a snackbar first. $page->script('window.scrollTo(0, 0)'); $page->assertScript("{$hidden} === false"); $page->script("materialToast('Pinned', { sticky: true })"); $page->assertScript("document.querySelector('[data-md-toast-snackbar]')?.textContent.includes('Pinned')"); $page->script('window.scrollTo(0, 400)'); $page->assertScript("{$hidden} === false") ->assertNoJavaScriptErrors(); }); it('draws the narrow collapsed width and centres the destinations', function () { $page = navigationExtrasProbe(); $page->assertScript("Math.round(document.querySelector('#narrow-rail [data-md-navigation-rail]').getBoundingClientRect().width) === 80") ->assertScript("getComputedStyle(document.querySelector('#narrow-rail [data-md-navigation-rail-destinations]')).justifyContent === 'safe center'") ->assertNoJavaScriptErrors(); }); it('takes a rail out of the layout entirely when it hides collapsed, and back over the page', function () { $page = navigationExtrasProbe(); $rail = "document.querySelector('#hiding-rail [data-md-navigation-rail]')"; $railWidth = "Math.round({$rail}.getBoundingClientRect().width)"; $page->assertScript("{$railWidth} > 200") ->click('#hiding-rail [data-md-navigation-rail-menu]') ->assertScript("{$railWidth} === 0") ->assertScript("! {$rail}.hasAttribute('data-md-open')"); // The only way back: an application's own menu button calling $store.rail.show(). $page->click('#open-hiding-rail') ->assertScript("{$rail}.hasAttribute('data-md-open')") ->assertScript("getComputedStyle(document.querySelector('#hiding-rail [data-md-navigation-rail-panel]')).display === 'flex'") ->assertNoJavaScriptErrors(); }); it('takes an adaptive rail out of the layout across the expanded class when it hides collapsed, standing again from large', function () { $page = navigationExtrasProbe(); $rail = "document.querySelector('#adaptive-hiding-rail [data-md-navigation-rail]')"; $railWidth = "Math.round({$rail}.getBoundingClientRect().width)"; // `medium` (600-839): the window collapses the rail, not the visitor, so M3's "collapsed rail // may never hide" holds even with hide-when-collapsed set — it stays at its collapsed width. $page->resize(839, 900) ->assertScript("{$railWidth} === 96") ->assertScript("! {$rail}.hasAttribute('data-md-open')") ->assertScript("document.documentElement.hasAttribute('data-rail-auto')") ->assertNoJavaScriptErrors(); // `expanded` (840-1199): nothing chosen yet, so the rail is away rather than shown collapsed. $page->resize(840, 900) ->assertScript("{$railWidth} === 0") ->assertScript("! {$rail}.hasAttribute('data-md-open')") ->assertScript("document.documentElement.hasAttribute('data-rail-auto')") ->assertNoJavaScriptErrors(); $page->resize(1199, 900) ->assertScript("{$railWidth} === 0") ->assertScript("document.documentElement.hasAttribute('data-rail-auto')"); // `large` (1200+): M3 starts an adaptive rail expanded by default, so it stands in the layout // again rather than staying away. $page->resize(1200, 900) ->assertScript("{$railWidth} === 256") ->assertScript("! {$rail}.hasAttribute('data-md-open')") ->assertScript("document.documentElement.hasAttribute('data-rail-auto')") ->assertNoJavaScriptErrors(); }); /** * One rail in the given mode, whose footer holds two lines an application styles by the rail's * value with style queries — the way `--md-navigation-rail-value` is meant to be read. An inline * script after the rail records the value and the lines' display while the document is still being * parsed, before Alpine exists: what the first paint shows. */ function railValueProbe(string $mode, int $width = 1280): mixed { Route::middleware('web')->get('/rail-value-probe/{mode}', fn (string $mode) => Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles

Signed in as Ada

A

Outside the rail

@livewireScripts BLADE, ['mode' => $mode])); return navigationReady(visit("/rail-value-probe/{$mode}")->resize($width, 900)); } const RAIL_VALUE = 'getComputedStyle('.RAIL.").getPropertyValue('--md-navigation-rail-value').trim()"; function railShows(string $line): string { return "getComputedStyle(document.querySelector('[data-test=\"{$line}\"]')).display !== 'none'"; } it('publishes a fixed rail\'s value from the first paint, for style queries in its content', function () { railValueProbe('collapsed') ->assertScript("window.firstPaint.alpine === 'undefined'") ->assertScript("window.firstPaint.value === 'collapsed'") ->assertScript("window.firstPaint.expandedOnly === 'none' && window.firstPaint.collapsedOnly !== 'none'") ->assertScript(RAIL_VALUE." === 'collapsed'") ->assertScript('! '.railShows('expanded-only')) ->assertScript(railShows('collapsed-only')) // Outside a rail the property is unset, so neither value's query matches. ->assertScript("getComputedStyle(document.querySelector('[data-test=\"outside\"]')).getPropertyValue('--md-navigation-rail-value') === ''") ->assertNoJavaScriptErrors(); railValueProbe('expanded') ->assertScript("window.firstPaint.value === 'expanded'") ->assertScript("window.firstPaint.expandedOnly !== 'none' && window.firstPaint.collapsedOnly === 'none'") ->assertScript(RAIL_VALUE." === 'expanded'") ->assertScript(railShows('expanded-only')) ->assertScript('! '.railShows('collapsed-only')) ->assertNoJavaScriptErrors(); }); it('moves an adaptive rail\'s value with the window and the menu button, in step with its width', function () { $page = railValueProbe('adaptive', 839) ->assertScript(railWidth(96)) ->assertScript(RAIL_VALUE." === 'collapsed'") ->assertScript('! '.railShows('expanded-only')); // `large`: M3 starts an adaptive rail expanded while nothing is chosen. $page->resize(1200, 900) ->assertScript(railWidth(256)) ->assertScript(RAIL_VALUE." === 'expanded'") ->assertScript(railShows('expanded-only')) ->assertScript('! '.railShows('collapsed-only')); // `expanded`: collapsed until the menu button widens it in place. $page->resize(840, 900) ->assertScript(RAIL_VALUE." === 'collapsed'") ->click('[data-md-navigation-rail-menu]') ->assertScript(railWidth(256)) ->assertScript(RAIL_VALUE." === 'expanded'") ->assertScript(railShows('expanded-only')) ->assertNoJavaScriptErrors(); }); it('reads expanded while a modal rail is open over the page, and collapsed once it closes', function () { $page = railValueProbe('modal', 1000) ->assertScript(RAIL_VALUE." === 'collapsed'") ->assertScript('! '.railShows('expanded-only')); $page->click('#open-rail') ->assertScript(RAIL.".hasAttribute('data-md-open')") ->assertScript(RAIL_VALUE." === 'expanded'") ->assertScript(railShows('expanded-only')) ->assertScript('! '.railShows('collapsed-only')); $page->keys('[data-md-navigation-rail-panel]', 'Escape') ->assertScript('! '.RAIL.".hasAttribute('data-md-open')") ->assertScript(RAIL_VALUE." === 'collapsed'") ->assertScript('! '.railShows('expanded-only')) ->assertNoJavaScriptErrors(); }); /** * Closes a rail with the given script and reports whether, sampling every few ms in the page * itself, it was ever caught part-way: the whole close in one round trip, since a separate * `script` and `assertScript` apiece already take as long as the 200ms exit (see ContainmentTest's * scrim fades). */ function caughtMidClose(mixed $page, string $close, string $partWay): bool { return $page->script(<< { {$close} for (let i = 0; i < 60; i++) { if ({$partWay}) return true await new Promise((resolve) => setTimeout(resolve, 5)) } return false })() JS) === true; } /** * Opened and done opening: no transition left on the element. Firefox creates a transition on its * next refresh tick, so straight after the change its computed style already reads the end value * while the entry has not begun, and a close then would start from where the entry did. */ function settled(string $element): string { return "{$element}.getAnimations().length === 0"; } /** Still drawn, and strictly between resting at the window's edge and gone past it. */ function slidingOut(string $panel): string { return "(getComputedStyle({$panel}).display !== 'none' && {$panel}.getBoundingClientRect().left < -2 && {$panel}.getBoundingClientRect().right > 2)"; } it('slides the compact rail out on close, rather than making it vanish', function () { $page = shellPage(599, 860) ->click('@shell-menu') ->assertScript(RAIL.".hasAttribute('data-md-open')") ->assertScript(settled(RAIL_PANEL).' && Math.round('.RAIL_PANEL.'.getBoundingClientRect().left) === 0'); expect(caughtMidClose($page, "document.querySelector('[data-md-navigation-rail-scrim]').click()", slidingOut(RAIL_PANEL)))->toBeTrue(); $page->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'") ->assertScript('! '.RAIL.".hasAttribute('data-md-closing')") ->assertNoJavaScriptErrors(); }); it('slides a rail that hides when collapsed out on close, rather than making it vanish', function () { $page = navigationExtrasProbe(); $rail = "document.querySelector('#hiding-rail [data-md-navigation-rail]')"; $panel = "{$rail}.querySelector(':scope > [data-md-navigation-rail-panel]')"; $page->click('#hiding-rail [data-md-navigation-rail-menu]') ->assertScript("Math.round({$rail}.getBoundingClientRect().width) === 0") ->click('#open-hiding-rail') ->assertScript("{$rail}.hasAttribute('data-md-open')") ->assertScript(settled($panel)." && Math.round({$panel}.getBoundingClientRect().left) === 0 && Math.round({$panel}.getBoundingClientRect().width) === 256"); expect(caughtMidClose($page, "{$rail}.querySelector(':scope > [data-md-navigation-rail-scrim]').click()", slidingOut($panel)))->toBeTrue(); $page->assertScript("getComputedStyle({$panel}).display === 'none'") ->assertScript("Math.round({$rail}.getBoundingClientRect().width) === 0") ->assertNoJavaScriptErrors(); }); it('fades a modal rail\'s scrim out on close while the rail stands collapsed in the layout', function () { $scrim = RAIL.".querySelector(':scope > [data-md-navigation-rail-scrim]')"; $page = railValueProbe('modal', 1000) ->click('#open-rail') ->assertScript(RAIL.".hasAttribute('data-md-open')") ->assertScript(settled($scrim)." && getComputedStyle({$scrim}).opacity === '1'"); $fading = "(getComputedStyle({$scrim}).display !== 'none' && parseFloat(getComputedStyle({$scrim}).opacity) > 0.02 && parseFloat(getComputedStyle({$scrim}).opacity) < 0.98)"; expect(caughtMidClose($page, "{$scrim}.click()", $fading))->toBeTrue(); // The panel goes straight back into the layout: only the scrim was over the page. $page->assertScript("getComputedStyle({$scrim}).display === 'none'") ->assertScript('getComputedStyle('.RAIL_PANEL.").position === 'sticky'") ->assertScript(railWidth(96)) ->assertNoJavaScriptErrors(); });