tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
The showcase is now an overview and one page per section behind a grouped navigation rail, moved between with wire:navigate, instead of one long page under a scrolling top bar. The switch exposed a WebKit bug in the menu: inside a focusable region, WebKit moves focus out of the closing popover before its toggle event, so Escape no longer returned focus to the trigger. The menu now reads it on beforetoggle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
127 lines
5.1 KiB
PHP
127 lines
5.1 KiB
PHP
<?php
|
|
|
|
const MORE = '#menus [aria-label="More"]';
|
|
|
|
function showcase(string $section = 'buttons')
|
|
{
|
|
return visit("/material/{$section}")->waitForEvent('networkidle')
|
|
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
|
}
|
|
|
|
function focused(string $expression): string
|
|
{
|
|
return "document.activeElement.{$expression}";
|
|
}
|
|
|
|
it('opens a menu on the first item and walks it with the keyboard', function () {
|
|
$page = showcase('menus')
|
|
->assertNoJavaScriptErrors()
|
|
->click(MORE)
|
|
->assertAttribute(MORE, 'aria-expanded', 'true')
|
|
->assertScript(focused("textContent.trim().startsWith('Copy link')"));
|
|
|
|
$page->keys(':focus', ['ArrowDown', 'ArrowDown'])
|
|
->assertScript(focused("textContent.trim().startsWith('Rename')"));
|
|
|
|
$page->keys(':focus', 'End')
|
|
->assertScript(focused("textContent.trim().startsWith('Delete')"));
|
|
|
|
$page->keys(':focus', 'ArrowDown')
|
|
->assertScript(focused("textContent.trim().startsWith('Copy link')"));
|
|
|
|
$page->keys(':focus', 'd')
|
|
->assertScript(focused("textContent.trim().startsWith('Download')"));
|
|
|
|
$page->keys(':focus', 'Escape')
|
|
->assertAttribute(MORE, 'aria-expanded', 'false')
|
|
->assertScript(focused("getAttribute('aria-label') === 'More'"));
|
|
});
|
|
|
|
it('opens a menu from the keyboard, on the last item with ArrowUp', function () {
|
|
$page = showcase('menus');
|
|
|
|
$page->script("document.querySelector('".MORE."').focus()");
|
|
|
|
$page->keys(':focus', 'ArrowUp')
|
|
->assertAttribute(MORE, 'aria-expanded', 'true')
|
|
->assertScript(focused("textContent.trim().startsWith('Delete')"));
|
|
});
|
|
|
|
it('opens a menu the moment the page can be used', function () {
|
|
// The guard against a light-dismiss press reopening the menu once measured from the page's
|
|
// time origin, and swallowed every click in the first quarter second.
|
|
visit('/material/menus')
|
|
->click(MORE)
|
|
->assertAttribute(MORE, 'aria-expanded', 'true');
|
|
});
|
|
|
|
it('closes a menu when an item is chosen, but not one that keeps it open', function () {
|
|
$page = showcase('menus');
|
|
|
|
$page->click(MORE)
|
|
->click('#menus [role="menuitem"]:has-text("Download")')
|
|
->assertAttribute(MORE, 'aria-expanded', 'false');
|
|
|
|
$page->click('#menus button:has-text("Sort")')
|
|
->click('#menus [role="menuitemcheckbox"]:has-text("Largest")')
|
|
->assertScript("document.querySelector('#menus [role=\"menu\"][aria-label=\"Sort\"]').matches(':popover-open')");
|
|
});
|
|
|
|
it('shows a tooltip on keyboard focus and hides it on Escape', function () {
|
|
$tooltip = "document.querySelector('#buttons [aria-label=\"Tonal\"] [popover]')";
|
|
|
|
$page = showcase();
|
|
|
|
// A key press first, so the browser is in keyboard modality, as a keyboard user would be —
|
|
// Firefox only counts a scripted focus as :focus-visible after one. Then focused directly
|
|
// rather than tabbed to: WebKit, like Safari on macOS, leaves buttons out of the Tab order
|
|
// unless full keyboard access is on.
|
|
$page->keys('#content', 'Tab');
|
|
$page->script("document.querySelector('#buttons [aria-label=\"Tonal\"]').focus()");
|
|
$page->assertScript(focused("getAttribute('aria-label') === 'Tonal'"))
|
|
->assertScript("{$tooltip}.matches(':popover-open')");
|
|
|
|
$page->keys(':focus', 'Escape')
|
|
->assertScript("! {$tooltip}.matches(':popover-open')");
|
|
});
|
|
|
|
it('moves a connected group\'s choice with the arrow keys', function () {
|
|
$checked = fn (string $value): string => "document.querySelector('#buttons input[name=\"showcase-theme\"][value=\"{$value}\"]').checked";
|
|
|
|
$page = showcase();
|
|
|
|
$page->script("document.querySelector('#buttons input[name=\"showcase-theme\"][value=\"system\"]').focus()");
|
|
|
|
$page->keys(':focus', 'ArrowLeft')
|
|
->assertScript($checked('dark'))
|
|
->assertScript("getComputedStyle(document.querySelector('#buttons input[value=\"dark\"]').parentElement).borderTopLeftRadius === '9999px'");
|
|
});
|
|
|
|
it('rounds a split button\'s trailing half while its menu is open', function () {
|
|
$trailing = "document.querySelector('[data-split=\"trailing\"]')";
|
|
|
|
showcase()
|
|
->click('[data-split="trailing"] >> nth=0')
|
|
->assertScript("{$trailing}.getAttribute('aria-expanded') === 'true'")
|
|
->assertScript("getComputedStyle({$trailing}).borderTopLeftRadius === '9999px'");
|
|
});
|
|
|
|
it('turns the FAB into a close button while its menu is open', function () {
|
|
$fab = "document.querySelector('button[aria-label=\"New\"]')";
|
|
|
|
$page = showcase()
|
|
->click('button[aria-label="New"]')
|
|
->assertScript("{$fab}.getAttribute('aria-expanded') === 'true'")
|
|
->assertScript(focused("textContent.trim() === 'Upload files'"));
|
|
|
|
$page->keys(':focus', 'Escape')
|
|
->assertScript("{$fab}.getAttribute('aria-expanded') === 'false'")
|
|
->assertScript(focused("getAttribute('aria-label') === 'New'"));
|
|
});
|
|
|
|
it('animates the loading indicator in the browser', function () {
|
|
$clock = "document.querySelector('#buttons [role=\"progressbar\"] svg').getCurrentTime()";
|
|
|
|
showcase()->assertScript("{$clock} > 0.1");
|
|
});
|