'2026-09-13', 'end' => '2026-09-15']; public int $renders = 0; public function touch(): void { $this->renders++; } public function render(): string { return <<<'BLADE'

expires: {{ $expires }}

birthday: {{ $birthday }}

delivery: {{ $delivery }}

trip: {{ json_encode($trip) }}

renders: {{ $renders }}

BLADE; } } function dateProbe(string $locale = 'en') { Livewire::component('date-probe', DateProbe::class); Route::middleware('web')->get('/date-probe/{locale}', function (string $locale) { app()->setLocale($locale); return Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles @livewireScripts BLADE); }); return visit("/date-probe/{$locale}")->waitForEvent('networkidle') ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'"); } /** A picker's day cell, by its ISO date. */ function day(string $picker, string $date): string { return "#{$picker}-picker [data-value=\"{$date}\"]"; } function focusedDay(string $date): string { return "document.activeElement.dataset.value === '{$date}'"; } it('opens the docked picker under its field and walks the grid from the keyboard', function () { $picker = "document.querySelector('#expires-field-picker')"; $page = dateProbe() ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript("{$picker}.matches(':popover-open') && {$picker}.dataset.presentation === 'docked'") ->assertAttribute('#expires-field', 'aria-expanded', 'true') ->assertScript("(() => { const field = document.querySelector('#expires-field').closest('.field-box').getBoundingClientRect(); const box = {$picker}.getBoundingClientRect(); return Math.abs(box.top - field.bottom - 4) < 2 && Math.abs(box.left - field.left) < 2; })()") ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'ArrowRight')->assertScript(focusedDay('2026-09-14')); $page->keys(':focus', 'ArrowDown')->assertScript(focusedDay('2026-09-21')); $page->keys(':focus', 'End')->assertScript(focusedDay('2026-09-26')); $page->keys(':focus', 'Home')->assertScript(focusedDay('2026-09-20')); $page->keys(':focus', 'ArrowUp')->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'PageDown') ->assertScript(focusedDay('2026-10-13')) ->assertScript("document.querySelector('#expires-field-month').textContent === 'October 2026'"); $page->keys(':focus', 'Enter') ->assertSeeIn('#expires', '2026-10-13') ->assertScript("! {$picker}.matches(':popover-open')") ->assertScript("document.activeElement.id === 'expires-field'") ->assertValue('#expires-field', '10/13/2026'); }); it('keeps focus and choice inside min and max', function () { $page = dateProbe() ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript(focusedDay('2026-09-13')); // Playwright will not press an aria-disabled cell, so the press is dispatched. $page->assertAttribute(day('expires-field', '2026-09-09'), 'aria-disabled', 'true') ->script("document.querySelector('".day('expires-field', '2026-09-09')."').click()"); $page->assertScript("! document.querySelector('".day('expires-field', '2026-09-09')."').hasAttribute('data-selected')") ->assertScript("document.querySelector('".day('expires-field', '2026-09-13')."').hasAttribute('data-selected')"); $page->keys(':focus', 'PageUp')->assertScript(focusedDay('2026-09-10')); $page->keys(':focus', 'ArrowLeft')->assertScript(focusedDay('2026-09-10')); $page->keys(':focus', 'Shift+PageDown')->assertScript(focusedDay('2026-10-20')); $page->keys(':focus', 'Enter')->assertSeeIn('#expires', '2026-10-20'); }); it('takes a date typed into the docked field once it is whole, and says when it cannot', function () { $page = dateProbe() ->type('#expires-field', '09/15/2026') ->assertSeeIn('#expires', '2026-09-15'); $page->type('#expires-field', '13/45/2026') ->keys('#expires-field', 'Enter') ->assertSee('Date does not match expected pattern: MM/DD/YYYY') ->assertAttribute('#expires-field', 'aria-invalid', 'true') ->assertSeeIn('#expires', '2026-09-15'); $page->type('#expires-field', '09/01/2026') ->keys('#expires-field', 'Enter') ->assertSee('Date not allowed: Sep 1, 2026') ->assertSeeIn('#expires', '2026-09-15'); }); it('opens the modal picker, jumps through the year grid and confirms with OK', function () { $dialog = "document.querySelector('#birthday-field-picker')"; $page = dateProbe() ->click('#birthday-field') ->assertScript("{$dialog}.matches(':modal')") ->assertSeeIn('#birthday-field-picker [data-datepicker-headline]', 'May 17, 2000') ->click('#birthday-field-picker [data-datepicker-nav]:not([data-docked]) [data-datepicker-menu-button]') ->assertScript("getComputedStyle(document.querySelector('#birthday-field-picker [data-datepicker-years]')).display !== 'none'") ->assertScript("document.activeElement.textContent.trim() === '2000'"); $page->click('#birthday-field-picker [data-datepicker-year]:has-text("1990")') ->assertSeeIn('#birthday-field-picker [data-datepicker-nav]', 'May 1990') ->click(day('birthday-field', '1990-05-03')) ->assertSeeIn('#birthday-field-picker [data-datepicker-headline]', 'May 3, 1990') ->assertSeeIn('#birthday', '2000-05-17'); $page->click('#birthday-field-picker [data-datepicker-confirm]') ->assertSeeIn('#birthday', '1990-05-03') ->assertScript("! {$dialog}.open"); }); it('closes on Escape without keeping the draft and hands focus back to the field', function () { $dialog = "document.querySelector('#birthday-field-picker')"; $page = dateProbe(); $page->script("document.querySelector('#birthday-field').focus()"); $page->keys('#birthday-field', 'Enter') ->assertScript("{$dialog}.open") ->assertScript(focusedDay('2000-05-17')); $page->keys(':focus', 'ArrowRight') ->keys(':focus', 'Space') ->assertSeeIn('#birthday-field-picker [data-datepicker-headline]', 'May 18, 2000'); $page->keys(':focus', 'Escape') ->assertScript("! {$dialog}.open") ->assertScript("document.activeElement.id === 'birthday-field'") ->assertSeeIn('#birthday', '2000-05-17'); // The docked picker too. $page->script("document.querySelector('[aria-controls=\"expires-field-picker\"][data-datepicker-toggle]').focus()"); $page->keys(':focus', 'Enter') ->assertScript("document.querySelector('#expires-field-picker').matches(':popover-open')") ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'Escape') ->assertScript("! document.querySelector('#expires-field-picker').matches(':popover-open')") ->assertScript("document.activeElement.id === 'expires-field'"); }); it('reads the modal input in the locale\'s format and explains a date it cannot take', function () { $dialog = "document.querySelector('#delivery-field-picker')"; $page = dateProbe() ->click('#delivery-field') ->assertScript("{$dialog}.matches(':modal')") ->assertScript("document.activeElement.id === 'delivery-field-entry'") ->assertSeeIn('#delivery-field-picker [data-datepicker-headline]', 'Entered date'); $page->type('#delivery-field-entry', '2026-03-07') ->click('#delivery-field-picker [data-datepicker-confirm]') ->assertSeeIn('#delivery-field-entry-support', 'Date does not match expected pattern: MM/DD/YYYY') ->assertAttribute('#delivery-field-entry', 'aria-invalid', 'true') ->assertScript("{$dialog}.open"); $page->type('#delivery-field-entry', '12/24/2025') ->keys('#delivery-field-entry', 'Enter') ->assertSeeIn('#delivery-field-entry-support', 'Date not allowed: Dec 24, 2025') ->assertScript("{$dialog}.open"); $page->type('#delivery-field-entry', '3/7/2026') ->assertSeeIn('#delivery-field-picker [data-datepicker-headline]', 'Mar 7, 2026') ->keys('#delivery-field-entry', 'Enter') ->assertSeeIn('#delivery', '2026-03-07') ->assertScript("! {$dialog}.open") ->assertValue('#delivery-field', '03/07/2026'); }); it('picks a range: a start, an end, and the days between', function () { $page = dateProbe() ->click('[aria-controls="trip-field-picker"][data-datepicker-toggle]') ->assertAttribute(day('trip-field', '2026-09-14'), 'aria-selected', 'true'); $page->click(day('trip-field', '2026-09-20')) ->assertScript("document.querySelector('".day('trip-field', '2026-09-20')."').hasAttribute('data-selected')") ->assertScript("! document.querySelector('".day('trip-field', '2026-09-14')."').hasAttribute('data-between')"); $page->click(day('trip-field', '2026-09-24')) ->assertScript("[...document.querySelectorAll('#trip-field-picker [data-between]')].map((cell) => cell.dataset.value).join() === '2026-09-21,2026-09-22,2026-09-23'") ->assertScript("document.querySelector('".day('trip-field', '2026-09-20')."').hasAttribute('data-start') && document.querySelector('".day('trip-field', '2026-09-24')."').hasAttribute('data-end')") ->assertSeeIn('#trip', '2026-09-15'); $page->click('#trip-field-picker [data-datepicker-confirm]') ->assertSeeIn('#trip', '{"start":"2026-09-20","end":"2026-09-24"}') ->assertValue('#trip-field', '09/20/2026 – 09/24/2026'); }); it('follows the locale: German weeks start on Monday, American ones on Sunday', function () { $firstWeekday = "document.querySelector('#expires-field-picker thead th').getAttribute('abbr')"; $page = dateProbe('de') ->assertValue('#expires-field', '13.09.2026') ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript("{$firstWeekday} === 'Montag'") ->assertScript("document.querySelector('#expires-field-month').textContent === 'September 2026'") ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'Home')->assertScript(focusedDay('2026-09-10')); $page->keys(':focus', 'Escape'); $page->type('#expires-field', '20.9.2026') ->assertSeeIn('#expires', '2026-09-20') ->assertAttribute('#expires-field', 'placeholder', 'DD.MM.YYYY'); dateProbe('en_US') ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript("{$firstWeekday} === 'Sunday'"); }); it('stays open, where it was, through a Livewire render', function () { $page = dateProbe() ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'ArrowRight')->assertScript(focusedDay('2026-09-14')); $page->script('window.eval("Livewire.first().touch()")'); $page->assertSeeIn('#renders', '1') ->assertScript("document.querySelector('#expires-field-picker').matches(':popover-open')") ->assertAttribute('#expires-field', 'aria-expanded', 'true') ->assertScript("document.querySelector('".day('expires-field', '2026-09-14')."').tabIndex === 0"); $page->keys(day('expires-field', '2026-09-14'), 'Enter')->assertSeeIn('#expires', '2026-09-14'); $page->click('#birthday-field') ->script('window.eval("Livewire.first().touch()")'); $page->assertSeeIn('#renders', '2') ->assertScript("document.querySelector('#birthday-field-picker').matches(':modal')"); }); it('opens a docked picker as a modal one on a compact window', function () { dateProbe() ->resize(400, 800) ->click('[aria-controls="expires-field-picker"][data-datepicker-toggle]') ->assertScript("document.querySelector('#expires-field-picker').matches(':modal')") ->assertScript("getComputedStyle(document.querySelector('#expires-field-picker [data-datepicker-header]')).display !== 'none'"); }); it('empties a date, or both ends of a range, with its clear button', function () { $page = dateProbe() ->assertScript("getComputedStyle(document.querySelector('#expires-field').closest('.field').querySelector('[data-field-clear]')).display !== 'none'"); $page->click('[data-datepicker]:has(#expires-field) [data-field-clear]') ->assertScript("document.querySelector('#expires').textContent === ''") ->assertValue('#expires-field', '') ->assertScript("document.activeElement.id === 'expires-field'") ->assertScript("getComputedStyle(document.querySelector('[data-datepicker]:has(#expires-field) [data-field-clear]')).display === 'none'"); $page->click('[data-datepicker]:has(#trip-field) [data-field-clear]') ->assertSeeIn('#trip', '{"start":null,"end":null}') ->assertValue('#trip-field', ''); });