From d37e23de0dd24dc489c8cce58bf41c9b6be18f6b Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Mon, 1 Aug 2022 07:44:10 -0700 Subject: [PATCH] feat(menu): Fix menu closing on menu item click. PiperOrigin-RevId: 464533483 --- list/harness.ts | 20 +++++++ list/lib/list.ts | 3 +- list/lib/listitem/harness.ts | 19 +++++++ list/list_test.ts | 2 - menu/harness.ts | 21 +++++++ menu/lib/menu.ts | 6 +- menu/menu_test.ts | 105 +++++++++++++++++++++++++++++++++++ 7 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 list/harness.ts create mode 100644 list/lib/listitem/harness.ts create mode 100644 menu/harness.ts create mode 100644 menu/menu_test.ts diff --git a/list/harness.ts b/list/harness.ts new file mode 100644 index 0000000000..3ba74c188f --- /dev/null +++ b/list/harness.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Harness} from '@material/web/testing/harness'; + +import {List} from './lib/list'; +import {ListItemHarness} from './lib/listitem/harness'; + +/** + * Test harness for list. + */ +export class ListHarness extends Harness { + /** @return List item harnesses. */ + getItems() { + return this.element.items.map((item) => new ListItemHarness(item)); + } +} diff --git a/list/lib/list.ts b/list/lib/list.ts index d625003608..51931d099c 100644 --- a/list/lib/list.ts +++ b/list/lib/list.ts @@ -18,7 +18,8 @@ export class List extends LitElement { items: ListItem[] = []; - @queryAssignedElements() protected assignedElements!: HTMLElement[]|null; + @queryAssignedElements({flatten: true}) + protected assignedElements!: HTMLElement[]|null; override firstUpdated(changedProperties: PropertyValues) { super.firstUpdated(changedProperties); diff --git a/list/lib/listitem/harness.ts b/list/lib/listitem/harness.ts new file mode 100644 index 0000000000..6ecef191d3 --- /dev/null +++ b/list/lib/listitem/harness.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Harness} from '@material/web/testing/harness'; + +import {ListItem} from './list-item'; + +/** + * Test harness for list item. + */ +export class ListItemHarness extends Harness { + override async getInteractiveElement() { + await this.element.updateComplete; + return this.element.renderRoot.querySelector('li') as HTMLElement; + } +} diff --git a/list/list_test.ts b/list/list_test.ts index f169c5010d..03256230ec 100644 --- a/list/list_test.ts +++ b/list/list_test.ts @@ -10,8 +10,6 @@ import './list-item'; import {Environment} from '@material/web/testing/environment'; import {html} from 'lit'; -import {MdList} from './list'; - const LIST_TEMPLATE = html` One diff --git a/menu/harness.ts b/menu/harness.ts new file mode 100644 index 0000000000..e054e320de --- /dev/null +++ b/menu/harness.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Harness} from '@material/web/testing/harness'; + +import {ListItemHarness} from '../list/lib/listitem/harness'; + +import {Menu} from './lib/menu'; + +/** + * Test harness for menu. + */ +export class MenuHarness extends Harness { + /** @return ListItem harnesses for the menu's items. */ + getItems() { + return this.element.items.map((item) => new ListItemHarness(item)); + } +} diff --git a/menu/lib/menu.ts b/menu/lib/menu.ts index 7c78ece06c..13ed347177 100644 --- a/menu/lib/menu.ts +++ b/menu/lib/menu.ts @@ -26,7 +26,7 @@ import {MDCMenuFoundation} from './foundation'; export type DefaultFocusState = keyof typeof DefaultFocusStateEnum; interface ActionDetail { - index: number; + item: ListItem; } /** @@ -293,9 +293,7 @@ export abstract class Menu extends LitElement { protected onAction(evt: CustomEvent) { const listElement = this.listElement; if (this.mdcFoundation && listElement) { - const index = evt.detail.index; - - const el = listElement.items[index]; + const el = evt.detail.item; if (el) { this.mdcFoundation.handleItemAction(el); diff --git a/menu/menu_test.ts b/menu/menu_test.ts new file mode 100644 index 0000000000..e0bf8fbb70 --- /dev/null +++ b/menu/menu_test.ts @@ -0,0 +1,105 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import './menu'; +import '../list/list-item'; + +import {Environment} from '@material/web/testing/environment'; +import {html} from 'lit'; + +import {MenuHarness} from './harness'; +import {MdMenu} from './menu'; + +describe('menu tests', () => { + let menu: MdMenu; + let harness: MenuHarness; + const env = new Environment(); + + beforeEach(async () => { + const el = env.render(getMenuTemplate()); + menu = el.querySelector('md-menu')!; + harness = await new MenuHarness(menu); + await env.waitForStability(); + }); + + describe('open/close', () => { + it('`show` method opens menu', async () => { + menu.show(); + await menu.updateComplete; + expect(menu.open).toBe(true); + }); + + it('close method closes menu', async () => { + menu.show(); + await menu.updateComplete; + + menu.close(); + await menu.updateComplete; + expect(menu.open).toBe(false); + }); + + it('closes the menu on click outside the menu', async () => { + menu.show(); + await menu.updateComplete; + + document.body.dispatchEvent(new MouseEvent('click')); + await menu.updateComplete; + expect(menu.open).toBe(false); + }); + + it('closes the menu on click on menu item', async () => { + menu.show(); + await menu.updateComplete; + + const item = harness.getItems()[0]; + await item.clickWithMouse(); + expect(menu.open).toBe(false); + }); + + it('closes the menu on TAB keypress', async () => { + menu.show(); + await menu.updateComplete; + + const menuSurface = menu.renderRoot.querySelector('md-menu-surface')!; + menuSurface.dispatchEvent(new KeyboardEvent('keydown', {key: 'Tab'})); + expect(menu.open).toBe(false); + }); + + it('closes the menu on ESC keypress', async () => { + menu.show(); + await menu.updateComplete; + + const menuSurface = menu.renderRoot.querySelector('md-menu-surface')!; + menuSurface.mdcRoot.dispatchEvent( + new KeyboardEvent('keydown', {key: 'Escape'})); + expect(menu.open).toBe(false); + }); + }); +}); + +function getMenuTemplate(propsInit: Partial = {}) { + return html` +
+ + + One + Two + Three + +
+ `; +} + +function setAnchorAndOpen(e: MouseEvent) { + const target = e.target as HTMLButtonElement; + const menu = target.nextElementSibling as MdMenu; + if (!menu.anchor) { + menu.anchor = target; + } + menu.show(); +} \ No newline at end of file