Files
livewire-material/tests/Feature/Components/SearchTest.php
T
Andreas Reinhold / reiniandClaude Fable 5.1 549d96b0dc Give search M3's icon entry point and its suggestions
M3 § Search names three entry points and only the bar existed; suggestions
before the first keystroke were missing too (plan step 24, audit
docs/audits/m3-alignment/inputs.md § Missing). `trigger="icon"` is the search
icon button — search as a secondary action, one 48px button that expands into
the full-screen view at any width, since an icon button has nothing to dock
under, and takes its focus back on close. The `suggestions` slot stands where
the results do until something is typed; the live region counts whichever of
the two is on screen and names suggestions as suggestions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:40:48 +02:00

104 lines
4.2 KiB
PHP

<?php
it('draws a search bar whose combobox controls the view', function () {
$html = (string) $this->blade('<x-search id="find" placeholder="Search shares" wire:model.live.debounce.300ms="query" />');
expect($html)
->toContain('data-search')
->toContain('role="search"')
->toContain('type="search"')
->toContain('placeholder="Search shares"')
->toContain('aria-label="Search shares"')
->toContain('role="combobox"')
->toContain('aria-haspopup="dialog"')
->toContain('aria-controls="find-view"')
->toContain('aria-expanded="false"')
->toContain('wire:model.live.debounce.300ms="query"')
->toContain('id="find-view"')
->toContain('data-search-clear')
->toContain('data-search-scrim')
->toContain('aria-label="Back"')
->toContain('materialSearch(false,')
->not->toContain('data-search-results');
// The state belongs to the combobox, never to the bare textbox ARIA gives it to.
expect($html)->toMatch('/<span\s+data-search-field[^>]*aria-expanded/s');
expect($html)->not->toMatch('/<input[^>]*aria-expanded/s');
});
it('announces how many results there are, politely', function () {
$html = (string) $this->blade('<x-search />');
expect($html)
->toContain('data-search-status')
->toContain('aria-live="polite"')
->toContain('aria-atomic="true"')
->toContain('No results')
->toContain('1 result')
->toContain(':count results');
});
it('shows the results as a list, or what to say when there are none', function () {
expect((string) $this->blade('<x-search><a href="/shares/1">holiday.zip</a></x-search>'))
->toContain('data-search-results role="list"')
->toContain('<a href="/shares/1">holiday.zip</a>')
->and((string) $this->blade('<x-search><x-slot:empty>No shares match.</x-slot:empty></x-search>'))
->toContain('No shares match.')
->not->toContain('data-search-results role="list"');
});
it('offers M3\'s search-icon entry point', function () {
$html = (string) $this->blade('<x-search id="find" trigger="icon" label="Search shares" />');
expect($html)
->toContain('data-trigger="icon"')
->toContain('data-search-trigger')
->toContain('x-ref="trigger"')
->toContain('x-on:click="expand()"')
->toContain('aria-label="Search shares"')
->toContain('aria-haspopup="dialog"')
->toContain('aria-controls="find-view"')
->toContain('materialSearch(false,')
->toContain("'icon'")
// The bar is still there: it becomes the expanded view's header.
->toContain('data-search-bar');
expect((string) $this->blade('<x-search />'))
->toContain('data-trigger="bar"')
->not->toContain('data-search-trigger');
});
it('shows suggestions until the first keystroke, then the results', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-search>
<a href="/shares/1">holiday.zip</a>
<x-slot:suggestions><a href="/recent">Recent shares</a></x-slot:suggestions>
</x-search>
BLADE);
expect($html)
->toContain('data-search-suggestions role="list"')
->toContain('x-show="query === \'\'"')
->toContain('Recent shares')
->toContain('x-show="query !== \'\'"')
->toContain('holiday.zip')
->toContain('1 suggestion')
->toContain(':count suggestions');
// Without the slot the results stand alone and are never hidden.
expect((string) $this->blade('<x-search><a href="/shares/1">holiday.zip</a></x-search>'))
->not->toContain('data-search-suggestions')
->not->toContain('x-show="query !== \'\'"');
});
it('names the input, trails the bar, and stays docked on request', function () {
$html = (string) $this->blade('<x-search label="Search your shares" icon="travel_explore" docked><x-slot:trailing><button>AR</button></x-slot:trailing></x-search>');
expect($html)
->toContain('placeholder="Search"')
->toContain('aria-label="Search your shares"')
->toContain('data-search-trailing')
->toContain('<button>AR</button>')
->toContain('materialSearch(true,');
});