From 2106509739f6910347558d17867fe7773647b700 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Wed, 31 Aug 2016 18:45:05 -0700 Subject: [PATCH 1/3] feat(menu): add keyboard events and accessibility --- e2e/components/menu/menu-page.ts | 13 +++++ e2e/components/menu/menu.e2e.ts | 84 +++++++++++++++++++++++++++++-- src/demo-app/menu/menu-demo.ts | 4 +- src/e2e-app/menu/menu-e2e.html | 2 + src/lib/core/keyboard/keycodes.ts | 13 +++++ src/lib/menu/README.md | 9 ++-- src/lib/menu/menu-directive.ts | 81 +++++++++++++++++++++++++++-- src/lib/menu/menu-item.ts | 37 ++++++-------- src/lib/menu/menu-trigger.ts | 56 +++++++++++++++++++-- src/lib/menu/menu.html | 2 +- src/lib/menu/menu.scss | 2 +- src/lib/menu/menu.ts | 8 +-- src/lib/tabs/tabs.ts | 9 +--- 13 files changed, 267 insertions(+), 53 deletions(-) create mode 100644 src/lib/core/keyboard/keycodes.ts diff --git a/e2e/components/menu/menu-page.ts b/e2e/components/menu/menu-page.ts index 25c4bf01d4e3..7e25d774004c 100644 --- a/e2e/components/menu/menu-page.ts +++ b/e2e/components/menu/menu-page.ts @@ -8,6 +8,8 @@ export class MenuPage { menu() { return element(by.css('.md-menu')); } + start() { return element(by.id('start')); } + trigger() { return element(by.id('trigger')); } triggerTwo() { return element(by.id('trigger-two')); } @@ -32,6 +34,17 @@ export class MenuPage { combinedMenu() { return element(by.css('.md-menu.combined')); } + // TODO(kara): move to common testing utility + pressKey(key: any): void { + browser.actions().sendKeys(key).perform(); + } + + // TODO(kara): move to common testing utility + expectFocusOn(el: ElementFinder): void { + expect(browser.driver.switchTo().activeElement().getInnerHtml()) + .toBe(el.getInnerHtml()); + } + expectMenuPresent(expected: boolean) { return browser.isElementPresent(by.css('.md-menu')).then((isPresent) => { expect(isPresent).toBe(expected); diff --git a/e2e/components/menu/menu.e2e.ts b/e2e/components/menu/menu.e2e.ts index fda43f6f8349..b247bc113ef8 100644 --- a/e2e/components/menu/menu.e2e.ts +++ b/e2e/components/menu/menu.e2e.ts @@ -12,7 +12,7 @@ describe('menu', () => { page.trigger().click(); page.expectMenuPresent(true); - expect(page.menu().getText()).toEqual("One\nTwo\nThree"); + expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour"); }); it('should close menu when area outside menu is clicked', () => { @@ -45,14 +45,14 @@ describe('menu', () => { it('should support multiple triggers opening the same menu', () => { page.triggerTwo().click(); - expect(page.menu().getText()).toEqual("One\nTwo\nThree"); + expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour"); page.expectMenuAlignedWith(page.menu(), 'trigger-two'); page.body().click(); page.expectMenuPresent(false); page.trigger().click(); - expect(page.menu().getText()).toEqual("One\nTwo\nThree"); + expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour"); page.expectMenuAlignedWith(page.menu(), 'trigger'); page.body().click(); @@ -66,6 +66,84 @@ describe('menu', () => { }); }); + describe('keyboard events', () => { + beforeEach(() => { + // click start button to avoid tabbing past navigation + page.start().click(); + page.pressKey(protractor.Key.TAB); + }); + + it('should auto-focus the first item when opened with keyboard', () => { + page.pressKey(protractor.Key.ENTER); + page.expectFocusOn(page.items(0)); + }); + + it('should not focus the first item when opened with mouse', () => { + page.trigger().click(); + page.expectFocusOn(page.trigger()); + }); + + it('should focus subsequent items when down arrow is pressed', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.DOWN); + page.expectFocusOn(page.items(1)); + }); + + it('should focus previous items when up arrow is pressed', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.DOWN); + page.pressKey(protractor.Key.UP); + page.expectFocusOn(page.items(0)); + }); + + it('should skip disabled items using arrow keys', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.DOWN); + page.pressKey(protractor.Key.DOWN); + page.expectFocusOn(page.items(3)); + + page.pressKey(protractor.Key.UP); + page.expectFocusOn(page.items(1)); + }); + + it('should close the menu when tabbing past items', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.TAB); + page.expectMenuPresent(false); + + page.start().click(); + page.pressKey(protractor.Key.TAB); + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.chord(protractor.Key.SHIFT, protractor.Key.TAB)); + page.expectMenuPresent(false); + }); + + it('should wrap back to menu when arrow keying past items', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.DOWN); + page.pressKey(protractor.Key.DOWN); + page.pressKey(protractor.Key.DOWN); + page.expectFocusOn(page.items(0)); + + page.pressKey(protractor.Key.UP); + page.expectFocusOn(page.items(3)); + }); + + it('should focus before and after trigger when tabbing past items', () => { + page.pressKey(protractor.Key.ENTER); + page.pressKey(protractor.Key.TAB); + page.expectFocusOn(page.triggerTwo()); + + // navigate back to trigger + page.pressKey(protractor.Key.chord(protractor.Key.SHIFT, protractor.Key.TAB)); + page.pressKey(protractor.Key.ENTER); + + page.pressKey(protractor.Key.chord(protractor.Key.SHIFT, protractor.Key.TAB)); + page.expectFocusOn(page.start()); + }); + + }); + describe('position - ', () => { it('should default menu alignment to "after below" when not set', () => { diff --git a/src/demo-app/menu/menu-demo.ts b/src/demo-app/menu/menu-demo.ts index 1d0d04bba99e..aafd42606ce5 100644 --- a/src/demo-app/menu/menu-demo.ts +++ b/src/demo-app/menu/menu-demo.ts @@ -12,8 +12,8 @@ export class MenuDemo { items = [ {text: 'Refresh'}, {text: 'Settings'}, - {text: 'Help'}, - {text: 'Sign Out', disabled: true} + {text: 'Help', disabled: true}, + {text: 'Sign Out'} ]; select(text: string) { this.selected = text; } diff --git a/src/e2e-app/menu/menu-e2e.html b/src/e2e-app/menu/menu-e2e.html index b77eee83e80a..057517b9c8fe 100644 --- a/src/e2e-app/menu/menu-e2e.html +++ b/src/e2e-app/menu/menu-e2e.html @@ -1,6 +1,7 @@
{{ selected }}
+ @@ -8,6 +9,7 @@ +