test(a11y): trace drawer focus before visible transition
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# P5-15-04-A11Y-02 — Mobile navigation focus lifecycle
|
||||
|
||||
## Reproduction and demonstrated cause
|
||||
|
||||
The unchanged native reduced-motion drawer case still fails. A nonmutating test diagnostic records the actual focus attempt: MUI tries to focus the dialog while both dialog and modal ancestor have `visibility:hidden`. Focus remains on Open navigation. By the settled observation the dialog is visible, but the application's `onEntered` callback only selects a navigation item if the dialog already has focus. That condition cannot recover the failed initial attempt. Immediate Escape therefore originates outside the modal, and Tab recovery can give the focus trap a different restore target.
|
||||
|
||||
The instrument calls the original HTMLElement focus implementation without changing its arguments/behaviour. It records only synthetic fixture DOM states. The existing hard/soft assertions are unchanged; no sleep, skip or expected-failure marker is added. This is transition/entry handling, not evidence that all navigation permanently traps the user.
|
||||
|
||||
PRES-E07 remains FAIL at this RED checkpoint. A minimal successor must focus from the visible entering phase, preserve a user's already-selected internal focus, and leave MUI containment/Escape/restoration protection enabled. Human screen-reader/device acceptance remains incomplete; production remains NO-GO.
|
||||
@@ -183,6 +183,16 @@ test('[P5-15-02 COLLECTION] thirteen areas retain native rendered entry across t
|
||||
|
||||
test('[P5-15-02] reduced-motion mobile drawer enters focus and Escape returns it', async ({ page, owner }, info) => {
|
||||
test.setTimeout(60_000);
|
||||
await page.addInitScript(() => {
|
||||
const original = HTMLElement.prototype.focus;
|
||||
const state = window as Window & { drawerFocusAttempts?: unknown[] };
|
||||
state.drawerFocusAttempts = [];
|
||||
HTMLElement.prototype.focus = function (options) {
|
||||
if (this.getAttribute('role') === 'dialog') state.drawerFocusAttempts!.push({ visibility: getComputedStyle(this).visibility,
|
||||
parentVisibility: this.closest('.MuiModal-root') ? getComputedStyle(this.closest('.MuiModal-root')!).visibility : null });
|
||||
original.call(this, options);
|
||||
};
|
||||
});
|
||||
const t = translations.en;
|
||||
const sample: Sample = { area: 'shell', path: '/jobs', width: 390, language: 'en', theme: 'light' };
|
||||
await enter(page, owner, sample);
|
||||
@@ -190,7 +200,11 @@ test('[P5-15-02] reduced-motion mobile drawer enters focus and Escape returns it
|
||||
await navigation.focus(); await navigation.press('Enter');
|
||||
const drawer = page.getByRole('dialog', { name: t.navigation, exact: true });
|
||||
await expect.soft(drawer.getByRole('button', { name: t.dashboard, exact: true })).toBeFocused();
|
||||
const focus = () => page.evaluate(() => ({ active: document.activeElement?.outerHTML.slice(0, 500), focusedInDialog: !!document.activeElement?.closest('[role=dialog]') }));
|
||||
const focus = () => page.evaluate(() => ({ active: document.activeElement?.outerHTML.slice(0, 500), focusedInDialog: !!document.activeElement?.closest('[role=dialog]'),
|
||||
attempts: (window as Window & { drawerFocusAttempts?: unknown[] }).drawerFocusAttempts,
|
||||
dialogs: Array.from(document.querySelectorAll('[role=dialog]')).map(node => ({ html: node.outerHTML.slice(0, 400), visibility: getComputedStyle(node).visibility,
|
||||
modalVisibility: node.closest('.MuiModal-root') ? getComputedStyle(node.closest('.MuiModal-root')!).visibility : null,
|
||||
focusable: node.getAttribute('tabindex'), bounds: node.getBoundingClientRect().toJSON() })) }));
|
||||
const beforeScan = await focus();
|
||||
await collect(page, info, sample, 'navigation-open');
|
||||
const afterScan = await focus();
|
||||
|
||||
Reference in New Issue
Block a user