*/ public array $days = [2]; public ?string $zone = 'Europe/Zurich'; public string $query = ''; public string $iconQuery = ''; public function render(): string { return <<<'BLADE'

days: {{ json_encode($days) }}

zone: {{ $zone }}

query: {{ $query }}

@foreach (array_filter(['holiday.zip', 'contract.pdf', 'review.mp4'], fn ($file) => $query === '' || str_contains($file, $query)) as $file) @endforeach No files match.
Files @foreach (array_filter(['holiday.zip', 'contract.pdf'], fn ($file) => str_contains($file, $iconQuery)) as $file) @endforeach
BLADE; } } function pickProbe() { Livewire::component('pick-probe', PickProbe::class); Route::middleware('web')->get('/pick-probe', fn () => Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles @livewireScripts BLADE)); return visit('/pick-probe')->waitForEvent('networkidle') ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'"); } it('keeps the values of filter chips as the property\'s own type', function () { pickProbe() ->assertAttribute('button[aria-pressed="true"]', 'aria-pressed', 'true') ->click('button:has-text("Mon")') ->assertSeeIn('#days', '[2,1]') ->click('button:has-text("Tue")') ->assertSeeIn('#days', '[1]') ->assertScript("[...document.querySelectorAll('[aria-pressed=\"true\"]')].map((chip) => chip.textContent.trim()).join() === 'Mon'"); }); it('filters a searchable choice as it is typed and chooses from the keyboard', function () { $list = "document.querySelector('#zone-field-list')"; $page = pickProbe() ->assertValue('#zone-field', 'Zurich') ->click('#zone-field') ->assertScript("{$list}.matches(':popover-open')") ->assertAttribute('#zone-field', 'aria-expanded', 'true'); $page->type('#zone-field', 'ber') ->assertScript("{$list}.querySelectorAll('[role=\"option\"]').length === 1"); $page->keys('#zone-field', 'Enter') ->assertSeeIn('#zone', 'Europe/Berlin') ->assertValue('#zone-field', 'Berlin') ->assertScript("! {$list}.matches(':popover-open')"); }); it('puts a searchable choice back on Escape and never chooses a disabled option', function () { $page = pickProbe()->click('#zone-field')->type('#zone-field', 'ber'); $page->keys('#zone-field', 'Escape') ->assertValue('#zone-field', 'Zurich') ->assertSeeIn('#zone', 'Europe/Zurich'); $page->click('#zone-field')->type('#zone-field', 'UTC')->keys('#zone-field', 'Enter') ->assertSeeIn('#zone', 'Europe/Zurich'); }); it('opens a searchable choice\'s list above a container that clips', function () { pickProbe() ->click('#zone-field') ->assertScript(<<<'JS' (() => { const list = document.querySelector('#zone-field-list'); const clip = document.querySelector('#clip').getBoundingClientRect(); const box = list.getBoundingClientRect(); const probe = document.elementFromPoint(box.left + box.width / 2, box.bottom - 8); return box.bottom > clip.bottom && list.contains(probe); })() JS); }); it('opens the search view with the results Livewire renders for the query', function () { $view = "document.querySelector('#find-view')"; // The combobox is the wrapper around the input, which carries `aria-expanded` (inputs.md IN-01). $page = pickProbe() ->click('#find') ->assertScript("getComputedStyle({$view}).display !== 'none'") ->assertAttribute('[role="combobox"]:has(> #find)', 'aria-expanded', 'true'); $page->type('#find', 'con') ->assertSeeIn('#query', 'con') ->assertScript("[...{$view}.querySelectorAll('button')].map((button) => button.textContent.trim()).join() === 'contract.pdf'"); $page->keys('#find', 'ArrowDown') ->assertScript("document.activeElement.textContent.trim() === 'contract.pdf'"); $page->keys(':focus', 'Escape') ->assertScript("getComputedStyle({$view}).display === 'none'") ->assertScript("document.activeElement.id === 'find'") ->assertAttribute('[role="combobox"]:has(> #find)', 'aria-expanded', 'false'); }); it('says so when nothing matches, and closes on a press outside or a chosen result', function () { $view = "document.querySelector('#find-view')"; $page = pickProbe() ->click('#find') ->type('#find', 'zzz') ->assertSeeIn('#find-view', 'No files match.'); // The docked view opens over a scrim that covers the rest of the page (inputs.md IN-09), so a // press outside the view lands on the scrim. $page->click('[data-md-search]:has(#find) [data-md-search-scrim]') ->assertScript("getComputedStyle({$view}).display === 'none'"); $page->click('#find') ->clear('#find') ->assertScript("{$view}.querySelectorAll('button').length === 3") ->click('#find-view button:has-text("review.mp4")') ->assertScript("getComputedStyle({$view}).display === 'none'"); }); /** * Presses the open docked search's scrim and samples, in the page and in the same round trip, * whether `$expression` (a number) was ever caught strictly between `$from` and `$to` while the * element was still displayed: the exit ran, rather than the element vanishing at once. */ function searchCloseSample(string $element, string $expression): string { return << { const root = document.querySelector('[data-md-search]:has(#find)') root.querySelector('[data-md-search-scrim]').dispatchEvent(new PointerEvent('pointerdown', { bubbles: true })) for (let i = 0; i < 80; i++) { const element = root.querySelector('{$element}') const value = {$expression} if (getComputedStyle(element).display !== 'none' && value > 0.02 && value < 0.98) return true await new Promise((resolve) => setTimeout(resolve, 5)) } return false })() JS; } it('fades the docked search\'s scrim out on close, rather than making it vanish', function () { $root = "document.querySelector('[data-md-search]:has(#find)')"; $page = pickProbe() ->click('#find') // Displayed as well as opaque: until the first frame of its entry the scrim is still // `display: none` at the open state's full opacity. ->assertScript("(({ display, opacity }) => display !== 'none' && opacity === '1')(getComputedStyle({$root}.querySelector('[data-md-search-scrim]')))"); expect($page->script(searchCloseSample('[data-md-search-scrim]', 'parseFloat(getComputedStyle(element).opacity)')))->toBeTrue(); $page->assertScript("getComputedStyle({$root}.querySelector('[data-md-search-scrim]')).display === 'none'"); }); it('closes the docked search view back into its bar, rather than making it vanish', function () { $view = "document.querySelector('#find-view')"; $page = pickProbe() ->click('#find') ->assertScript("getComputedStyle({$view}).display !== 'none' && getComputedStyle({$view}).opacity === '1'"); expect($page->script(searchCloseSample('[data-md-search-view]', 'parseFloat(getComputedStyle(element).opacity)')))->toBeTrue(); $page->assertScript("getComputedStyle({$view}).display === 'none'"); }); it('takes the whole screen on a compact window, with a back arrow', function () { $page = pickProbe()->resize(400, 800); $page->click('#find') ->assertScript("document.querySelector('[data-md-search]').hasAttribute('data-md-full-screen')") ->assertScript("(() => { const box = document.querySelector('#find-view').getBoundingClientRect(); return box.top === 0 && box.width === 400; })()") ->click('[data-md-search]:has(#find) [data-md-search-back]') ->assertScript("! document.querySelector('[data-md-search]').hasAttribute('data-md-open')"); }); it('says how many results there are, politely, as they change', function () { $status = "document.querySelector('[data-md-search]:has(#find) [data-md-search-status]')"; $page = pickProbe() ->assertScript("{$status}.getAttribute('aria-live') === 'polite' && {$status}.getAttribute('aria-atomic') === 'true'") ->click('#find') ->assertScript("{$status}.textContent === '3 results'"); $page->type('#find', 'con') ->assertSeeIn('#query', 'con') ->assertScript("{$status}.textContent === '1 result'"); $page->type('#find', 'zzz') ->assertSeeIn('#query', 'zzz') ->assertScript("{$status}.textContent === 'No results'"); // Closed from a result (Escape in the field itself would first clear the query), the region // falls silent. $page->type('#find', 'hol') ->assertSeeIn('#query', 'hol') ->assertScript("{$status}.textContent === '1 result'"); $page->keys('#find', 'ArrowDown') ->assertScript("document.activeElement.textContent.trim() === 'holiday.zip'"); $page->keys(':focus', 'Escape') ->assertScript("{$status}.textContent === ''"); }); it('expands the search icon into the full-screen view, and hands focus back to the icon on close', function () { $root = "document.querySelector('[data-md-search]:has(#find-icon)')"; $trigger = "{$root}.querySelector('[data-md-search-trigger]')"; $page = pickProbe() ->assertScript("getComputedStyle({$root}.querySelector('[data-md-search-bar]')).display === 'none'") ->assertScript("{$root}.getBoundingClientRect().width === 48 && {$trigger}.getAttribute('aria-expanded') === 'false'"); $page->click('[data-md-search]:has(#find-icon) [data-md-search-trigger]') ->assertScript("{$root}.hasAttribute('data-md-open') && {$root}.hasAttribute('data-md-full-screen')") ->assertScript("document.activeElement.id === 'find-icon'") ->assertScript("(() => { const view = {$root}.querySelector('[data-md-search-view]').getBoundingClientRect(); return view.top === 0 && view.left === 0 && view.width === innerWidth; })()") // The toolbar does not shift under the expanded search. ->assertScript("{$root}.getBoundingClientRect().width === 48"); $page->keys('#find-icon', 'Escape') ->assertScript("! {$root}.hasAttribute('data-md-open')") ->assertScript("document.activeElement === {$trigger}") ->assertScript("getComputedStyle({$trigger}).display !== 'none'"); }); /** * Closes a full-screen search from its back arrow and samples, in the page, for a moment in its * exit where the layout is still full screen — the root marked, the header bar fixed at the top — * with both the bar and the view part-way through their fade, rather than the bar gone back to * its resting form (or to nothing, behind the icon) on the first frame. */ function fullScreenCloseSample(string $input): string { return << { const root = document.querySelector('[data-md-search]:has(#{$input})') root.querySelector('[data-md-search-back]').click() for (let i = 0; i < 80; i++) { const bar = getComputedStyle(root.querySelector('[data-md-search-bar]')) const view = getComputedStyle(root.querySelector('[data-md-search-view]')) const fading = (style) => style.display !== 'none' && parseFloat(style.opacity) > 0.02 && parseFloat(style.opacity) < 0.98 if (! root.hasAttribute('data-md-open') && root.hasAttribute('data-md-full-screen') && bar.position === 'fixed' && fading(bar) && fading(view)) return true await new Promise((resolve) => setTimeout(resolve, 5)) } return false })() JS; } it('closes the full-screen view and its bar together, back into the search icon', function () { $root = "document.querySelector('[data-md-search]:has(#find-icon)')"; $trigger = "{$root}.querySelector('[data-md-search-trigger]')"; $page = pickProbe() ->click('[data-md-search]:has(#find-icon) [data-md-search-trigger]') ->assertScript("{$root}.hasAttribute('data-md-full-screen')") // Its entry run: Firefox reads a transition's end value until its next refresh tick, so an // opacity of 1 alone does not say the view has finished fading in. ->assertScript("{$root}.getAnimations({ subtree: true }).length === 0"); expect($page->script(fullScreenCloseSample('find-icon')))->toBeTrue(); $page->assertScript("! {$root}.hasAttribute('data-md-full-screen')") ->assertScript("getComputedStyle({$root}.querySelector('[data-md-search-bar]')).display === 'none'") ->assertScript("getComputedStyle({$root}.querySelector('[data-md-search-view]')).display === 'none'") ->assertScript("document.activeElement === {$trigger}") // The view does not open again when the trap's return of focus lands on the icon. ->assertScript("! {$root}.hasAttribute('data-md-open')"); }); it('closes a compact window\'s full-screen view and its bar together, back into the bar', function () { $root = "document.querySelector('[data-md-search]:has(#find)')"; $page = pickProbe()->resize(400, 800); $page->click('#find') ->assertScript("{$root}.hasAttribute('data-md-full-screen')") ->assertScript("{$root}.getAnimations({ subtree: true }).length === 0"); expect($page->script(fullScreenCloseSample('find')))->toBeTrue(); $page->assertScript("! {$root}.hasAttribute('data-md-full-screen')") ->assertScript("(({ position, display, opacity }) => position === 'relative' && display !== 'none' && opacity === '1')(getComputedStyle({$root}.querySelector('[data-md-search-bar]')))") ->assertScript("getComputedStyle({$root}.querySelector('[data-md-search-view]')).display === 'none'") ->assertScript("document.activeElement.id === 'find'") ->assertScript("! {$root}.hasAttribute('data-md-open')"); }); it('shows the suggestions until the first key, then the results, and counts whichever is on screen', function () { $root = "document.querySelector('[data-md-search]:has(#find-icon)')"; $shown = fn (string $list): string => "{$root}.querySelector('[data-md-search-{$list}]').checkVisibility()"; $page = pickProbe() ->click('[data-md-search]:has(#find-icon) [data-md-search-trigger]') ->assertScript("({$shown('suggestions')}) && ! ({$shown('results')})") ->assertScript("{$root}.querySelector('[data-md-search-status]').textContent === '3 suggestions'"); $page->type('#find-icon', 'h') ->assertScript("! ({$shown('suggestions')}) && ({$shown('results')})") ->assertScript("{$root}.querySelector('[data-md-search-status]').textContent === '1 result'"); });