Skip to content

Commit

Permalink
complete keyboard menu navigation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gossi committed May 11, 2024
1 parent 655f2ac commit 9bfb3a0
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ export class MenuNavigation implements NavigationPattern {
handle(bag: NavigationParameterBag): NavigationParameterBag {
const { event } = bag;

console.log(event);

// navigation handlers
if (event.type === 'keydown') {
this.navigateWithKeyboard(event as KeyboardEvent);
Expand All @@ -75,8 +73,6 @@ export class MenuNavigation implements NavigationPattern {
}

if (event.key === 'ArrowLeft') {
console.log('hide submenu');

this.hideSubmenu();
}

Expand Down Expand Up @@ -156,7 +152,6 @@ export class MenuNavigation implements NavigationPattern {
if ((this.control.element as MenuElement)[FOCUS_ON_OPEN] !== false) {
if (this.control.items.length > 0) {
this.control.items[0].focus();
console.log('focus first item after show', this.control.items[0]);
}
}
}
Expand All @@ -175,11 +170,8 @@ export class MenuNavigation implements NavigationPattern {
// @ts-expect-error yep, we add out own secret type
const trigger = this.control.element[OPENER] as HTMLElement | undefined;

console.log('focus trigger', trigger);

if (trigger) {
trigger.focus();
console.log('focus trigger', trigger);
}
}
}
Expand Down
133 changes: 0 additions & 133 deletions incubator/aria-navigator/tests/menu/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { describe, expect, it } from 'vitest';
import { Menu } from '../../src';
import { createRefactorMenu, getItems } from './-shared';

async function settled() {
await new Promise((resolve) => window.setTimeout(resolve, 1));
}

describe('Menu Navigation', () => {
// describe('When Focus', () => {
// it('focus activates the first item', () => {
Expand Down Expand Up @@ -165,134 +161,5 @@ describe('Menu Navigation', () => {
).toBeTruthy();
});
});

describe.shuffle('submenu navigation', () => {
describe('open with `ArrowRight`', () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));

it('use `ArrowRight` to open submenu', () => {
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

expect(fourthItem.getAttribute('tabindex')).toBe('0');

refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));

// await settled();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
});

it('has focus on the first item of the submenu', async () => {
const share = new Menu(shareMenu);
const firstItem = share.items[0];

await settled();

expect(firstItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(firstItem);
});
});

describe('open with `Enter`', () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));

it('use `Enter` to open submenu', () => {
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

expect(fourthItem.getAttribute('tabindex')).toBe('0');

// Apparently, it is not really possible to trigger native behavior
// The keyboard event is the same as the native recorded one,
// though the native one has an `isTrusted` property set to true, which is
// not simulatable.
//
// My hunch is, it needs to be a trusted event in order to trigger the popover
//
// @see https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted

// fourthItem.dispatchEvent(
// new KeyboardEvent('keydown', {
// key: 'Enter',
// code: 'Enter',
// which: 13,
// keyCode: 13,
// view: window,
// bubbles: true
// })
// );

// `click()` invokes the popover and is the `Enter` simulation
// the pointer tests checking for hover anyway, so seems good here
fourthItem.click();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
});

it('has focus on the first item of the submenu', async () => {
const share = new Menu(shareMenu);
const firstItem = share.items[0];

await settled();

expect(firstItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(firstItem);
});
});

describe('close with `ArrowLeft`', async () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);
const share = new Menu(shareMenu);
const codeItem = share.items[0];

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
await settled();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
expect(codeItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(codeItem);

it('use `ArrowLeft` to close submenu', () => {
// await settled();
shareMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
expect(shareMenu.matches(':popover-open')).toBeFalsy();
});

it('has focus moved to the trigger of the submenu', async () => {
await settled();
expect(document.activeElement).toBe(fourthItem);
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';

import { Menu } from '../../../../../src';
import { settled } from '../../../../utils';
import { createRefactorMenu, getItems } from '../../../-shared';

describe('Menu Navigation', () => {
describe('submenu navigation', () => {
describe('close with `ArrowLeft`', async () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);
const share = new Menu(shareMenu);
const codeItem = share.items[0];

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
await settled();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
expect(codeItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(codeItem);

it('use `ArrowLeft` to close submenu', () => {
// await settled();
shareMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
expect(shareMenu.matches(':popover-open')).toBeFalsy();
});

it('has focus moved to the trigger of the submenu', async () => {
await settled();
expect(document.activeElement).toBe(fourthItem);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest';

import { Menu } from '../../../../../src';
import { settled } from '../../../../utils';
import { createRefactorMenu, getItems } from '../../../-shared';

describe('Menu Navigation', () => {
describe('submenu navigation', () => {
describe('open with `ArrowRight`', () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));

it('use `ArrowRight` to open submenu', () => {
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

expect(fourthItem.getAttribute('tabindex')).toBe('0');

refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));

expect(shareMenu.matches(':popover-open')).toBeTruthy();
});

it('has focus on the first item of the submenu', async () => {
const share = new Menu(shareMenu);
const firstItem = share.items[0];

await settled();

expect(firstItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(firstItem);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it } from 'vitest';

import { Menu } from '../../../../../src';
import { settled } from '../../../../utils';
import { createRefactorMenu, getItems } from '../../../-shared';

describe('Menu Navigation', () => {
describe('submenu navigation', () => {
describe('open with `Enter`', () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));

it('use `Enter` to open submenu', () => {
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

expect(fourthItem.getAttribute('tabindex')).toBe('0');

// Apparently, it is not really possible to trigger native behavior
// The keyboard event is the same as the native recorded one,
// though the native one has an `isTrusted` property set to true, which is
// not simulatable.
//
// My hunch is, it needs to be a trusted event in order to trigger the popover
//
// @see https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted

// fourthItem.dispatchEvent(
// new KeyboardEvent('keydown', {
// key: 'Enter',
// code: 'Enter',
// which: 13,
// keyCode: 13,
// view: window,
// bubbles: true
// })
// );

// `click()` invokes the popover and is the `Enter` simulation
// the pointer tests checking for hover anyway, so seems good here
fourthItem.click();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
});

it('has focus on the first item of the submenu', async () => {
const share = new Menu(shareMenu);
const firstItem = share.items[0];

await settled();

expect(firstItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(firstItem);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest';

import { Menu } from '../../../../../src';
import { settled } from '../../../../utils';
import { createRefactorMenu, getItems } from '../../../-shared';

describe('Menu Navigation', () => {
describe('submenu navigation', () => {
describe('close with `Escape`', async () => {
const { refactorMenu, shareMenu } = createRefactorMenu();

const menu = new Menu(refactorMenu);
const share = new Menu(shareMenu);
const codeItem = share.items[0];

expect(shareMenu.matches(':popover-open')).toBeFalsy();

const { fourthItem } = getItems(menu);

refactorMenu.dispatchEvent(new FocusEvent('focusin'));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));

refactorMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
await settled();

expect(shareMenu.matches(':popover-open')).toBeTruthy();
expect(codeItem.getAttribute('tabindex')).toBe('0');
expect(document.activeElement).toBe(codeItem);

it('use `Escape` to close submenu', () => {
// this is our "Escape" *cough* *cough*
// see enter.test.ts for more on this
codeItem.click();

shareMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
expect(shareMenu.matches(':popover-open')).toBeFalsy();
});

it('has focus moved to the trigger of the submenu', async () => {
await settled();
expect(document.activeElement).toBe(fourthItem);
});
});
});
});
3 changes: 3 additions & 0 deletions incubator/aria-navigator/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function settled() {
await new Promise((resolve) => window.setTimeout(resolve, 1));
}

0 comments on commit 9bfb3a0

Please sign in to comment.