'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'"); } class DateFormatProbe extends Component { public ?string $sunday = '2026-09-13'; public ?string $iso = '2026-09-13'; public ?string $dotted = '2026-09-13'; /** @var array{start: ?string, end: ?string} */ public array $span = ['start' => '2026-09-13', 'end' => '2026-09-15']; public function render(): string { return <<<'BLADE'

sunday: {{ $sunday }}

iso: {{ $iso }}

dotted: {{ $dotted }}

span: {{ json_encode($span) }}

BLADE; } } function dateFormatProbe(string $locale = 'en') { Livewire::component('date-format-probe', DateFormatProbe::class); Route::middleware('web')->get('/date-format-probe/{locale}', function (string $locale) { app()->setLocale($locale); return Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles @livewireScripts BLADE); }); return visit("/date-format-probe/{$locale}")->waitForEvent('networkidle') ->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'"); } class ScopedDateProbe extends Component { public ?string $early = '2026-09-13'; public ?string $late = '2026-09-13'; public function render(): string { return <<<'BLADE'
BLADE; } } function scopedDateProbe() { Livewire::component('scoped-date-probe', ScopedDateProbe::class); Route::middleware('web')->get('/scoped-date-probe', fn () => Blade::render(<<<'BLADE' @vite(config('livewire-material.showcase.vite')) @livewireStyles @livewireScripts BLADE)); return visit('/scoped-date-probe')->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('[data-md-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-header] [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-header] [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-header] [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-header] [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-header] [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('[data-md-field]').querySelector('[data-md-field-clear]')).display !== 'none'"); $page->click('[data-datepicker]:has(#expires-field) [data-md-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-md-field-clear]')).display === 'none'"); $page->click('[data-datepicker]:has(#trip-field) [data-md-field-clear]') ->assertSeeIn('#trip', '{"start":null,"end":null}') ->assertValue('#trip-field', ''); }); it('starts the week on the day week-start names, whatever the locale says', function () { $page = dateFormatProbe('de') ->assertValue('#sunday-field', '13.09.2026') ->click('[aria-controls="sunday-field-picker"][data-datepicker-toggle]') ->assertScript("document.querySelector('#sunday-field-picker thead th').getAttribute('abbr') === 'Sonntag'") ->assertScript("document.querySelector('#sunday-field-picker tbody td').dataset.value === '2026-08-30'") ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'End')->assertScript(focusedDay('2026-09-19')); $page->keys(':focus', 'Home')->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'Enter') ->assertSeeIn('#sunday', '2026-09-13') ->assertValue('#sunday-field', '13.09.2026'); }); it('shows and reads a year-first format and still binds Y-m-d', function () { $page = dateFormatProbe() ->assertValue('#iso-field', '2026-09-13') ->assertAttribute('#iso-field', 'placeholder', 'YYYY-MM-DD') ->type('#iso-field', '2026-10-01') ->assertSeeIn('#iso', '2026-10-01'); $page->type('#iso-field', '10/02/2026') ->keys('#iso-field', 'Enter') ->assertSee('Date does not match expected pattern: YYYY-MM-DD') ->assertSeeIn('#iso', '2026-10-01'); $page->click('[aria-controls="iso-field-picker"][data-datepicker-toggle]') ->assertScript(focusedDay('2026-10-01')); $page->keys(':focus', 'ArrowRight')->assertScript(focusedDay('2026-10-02')); $page->keys(':focus', 'Enter') ->assertSeeIn('#iso', '2026-10-02') ->assertValue('#iso-field', '2026-10-02'); }); it('reads the dialog\'s text field in the given format, not the locale\'s', function () { $dialog = "document.querySelector('#dotted-field-picker')"; $page = dateFormatProbe() ->assertValue('#dotted-field', '13.09.2026') ->click('#dotted-field') ->assertScript("{$dialog}.matches(':modal')") ->assertScript("document.activeElement.id === 'dotted-field-entry'") ->assertValue('#dotted-field-entry', '13.09.2026') ->assertAttribute('#dotted-field-entry', 'placeholder', 'DD.MM.YYYY'); $page->type('#dotted-field-entry', '2026-10-01') ->keys('#dotted-field-entry', 'Enter') ->assertSeeIn('#dotted-field-entry-support', 'Date does not match expected pattern: DD.MM.YYYY') ->assertScript("{$dialog}.open"); $page->type('#dotted-field-entry', '1.10.2026') ->assertSeeIn('#dotted-field-picker [data-datepicker-header] [data-datepicker-headline]', 'Oct 1, 2026') ->keys('#dotted-field-entry', 'Enter') ->assertSeeIn('#dotted', '2026-10-01') ->assertScript("! {$dialog}.open") ->assertValue('#dotted-field', '01.10.2026'); }); it('lays out and shows a range in the given first day and format', function () { $dialog = "document.querySelector('#span-field-picker')"; $page = dateFormatProbe()->assertValue('#span-field', '13/09/2026 – 15/09/2026'); $page->script("document.querySelector('#span-field').focus()"); $page->keys('#span-field', 'Enter') ->assertScript("{$dialog}.matches(':modal')") ->assertScript("document.querySelector('#span-field-picker thead th').getAttribute('abbr') === 'Saturday'") ->assertScript(focusedDay('2026-09-13')); $page->keys(':focus', 'Home')->assertScript(focusedDay('2026-09-12')); $page->keys(':focus', 'End')->assertScript(focusedDay('2026-09-18')); $page->click(day('span-field', '2026-09-20')) ->click(day('span-field', '2026-09-24')) ->click('#span-field-picker [data-datepicker-confirm]') ->assertSeeIn('#span', '{"start":"2026-09-20","end":"2026-09-24"}') ->assertValue('#span-field', '20/09/2026 – 24/09/2026'); }); it('keeps each picker\'s own settings inside a page\'s outer x-data scope', function () { // Settings assigned in init() without being declared would land on the outermost scope, // where the last picker's null min and Monday start would overwrite the first's. $page = scopedDateProbe() ->click('[aria-controls="early-field-picker"][data-datepicker-toggle]') ->assertScript(focusedDay('2026-09-13')); $page->assertAttribute(day('early-field', '2026-09-09'), 'aria-disabled', 'true') ->assertScript('! (\'min\' in Alpine.$data(document.querySelector(\'[x-data="{ step: 1 }"]\')))'); });