*/ 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'"); }); 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'"); }); 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'"); });