From 8d53068fdca10d88e8e5f4d2f9d2a854ff656da1 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Mon, 27 Aug 2018 11:12:51 -0400 Subject: [PATCH] fix(drawer): Fix exports and closure tests (#3424) --- closure_externs.js | 2 +- packages/mdc-drawer/index.js | 37 ++++++++---- packages/mdc-drawer/util.js | 9 ++- test/unit/mdc-drawer/mdc-drawer.test.js | 80 +++++++++---------------- 4 files changed, 65 insertions(+), 63 deletions(-) diff --git a/closure_externs.js b/closure_externs.js index 92269004c75..01d137efad0 100644 --- a/closure_externs.js +++ b/closure_externs.js @@ -112,4 +112,4 @@ class FocusTrapInstance { * @param {!Element} element * @param {FocusTrapCreateOptions=} createOptions */ -mdc.thirdparty.focusTrap.default; +mdc.thirdparty.focusTrap; diff --git a/packages/mdc-drawer/index.js b/packages/mdc-drawer/index.js index 8f4da21019f..631c498d7f6 100644 --- a/packages/mdc-drawer/index.js +++ b/packages/mdc-drawer/index.js @@ -23,16 +23,18 @@ import {MDCComponent} from '@material/base/index'; import MDCDismissibleDrawerFoundation from './dismissible/foundation'; import MDCModalDrawerFoundation from './modal/foundation'; +import MDCDrawerAdapter from './adapter'; import {MDCList} from '@material/list/index'; import MDCListFoundation from '@material/list/foundation'; import {strings} from './constants'; import * as util from './util'; +import createFocusTrap from 'focus-trap'; /** * @extends {MDCComponent} * @final */ -export class MDCDrawer extends MDCComponent { +class MDCDrawer extends MDCComponent { /** * @param {...?} args */ @@ -49,6 +51,15 @@ export class MDCDrawer extends MDCComponent { this.handleTransitionEnd_; /** @private {!Function} */ + this.focusTrapFactory_; + + /** @private {!FocusTrapInstance} */ + this.focusTrap_; + + /** @private {?Element} */ + this.scrim_; + + /** @private {?Function} */ this.handleScrimClick_; } @@ -80,9 +91,12 @@ export class MDCDrawer extends MDCComponent { } } - initialize() { - const list = MDCList.attachTo(this.root_.querySelector(`.${MDCListFoundation.cssClasses.ROOT}`)); + initialize( + focusTrapFactory = createFocusTrap) { + const listEl = /** @type {!Element} */ (this.root_.querySelector(`.${MDCListFoundation.cssClasses.ROOT}`)); + const list = MDCList.attachTo(listEl); list.wrapFocus = true; + this.focusTrapFactory_ = focusTrapFactory; } initialSyncWithDOM() { @@ -90,10 +104,10 @@ export class MDCDrawer extends MDCComponent { if (this.root_.classList.contains(MODAL)) { const {SCRIM_SELECTOR} = MDCDismissibleDrawerFoundation.strings; - this.scrim_ = this.root_.parentElement.querySelector(SCRIM_SELECTOR); - this.handleScrimClick_ = () => this.foundation_.handleScrimClick(); + this.scrim_ = /** @type {!Element} */ (this.root_.parentElement.querySelector(SCRIM_SELECTOR)); + this.handleScrimClick_ = () => /** @type {!MDCModalDrawerFoundation} */ (this.foundation_).handleScrimClick(); this.scrim_.addEventListener('click', this.handleScrimClick_); - this.focusTrap_ = util.createFocusTrapInstance(this.root_); + this.focusTrap_ = util.createFocusTrapInstance(this.root_, this.focusTrapFactory_); } this.handleKeydown_ = (evt) => this.foundation_.handleKeydown(evt); @@ -109,8 +123,9 @@ export class MDCDrawer extends MDCComponent { const {MODAL} = MDCDismissibleDrawerFoundation.cssClasses; if (this.root_.classList.contains(MODAL)) { - this.scrim_.removeEventListener('click', this.handleScrimClick_); - this.focusTrap_.destroy(); + this.scrim_.removeEventListener('click', /** @type {!Function} */ (this.handleScrimClick_)); + // Ensure drawer is closed to hide scrim and release focus + this.open = false; } } @@ -137,8 +152,8 @@ export class MDCDrawer extends MDCComponent { activeNavItemEl.focus(); } }, - notifyClose: () => this.emit(strings.CLOSE_EVENT, null, true /* shouldBubble */), - notifyOpen: () => this.emit(strings.OPEN_EVENT, null, true /* shouldBubble */), + notifyClose: () => this.emit(strings.CLOSE_EVENT, {}, true /* shouldBubble */), + notifyOpen: () => this.emit(strings.OPEN_EVENT, {}, true /* shouldBubble */), trapFocus: () => this.focusTrap_.activate(), releaseFocus: () => this.focusTrap_.deactivate(), })); @@ -154,3 +169,5 @@ export class MDCDrawer extends MDCComponent { } } } + +export {MDCDrawer, MDCDismissibleDrawerFoundation, MDCModalDrawerFoundation, util}; diff --git a/packages/mdc-drawer/util.js b/packages/mdc-drawer/util.js index 7cdbf1d7479..0b364d64c94 100644 --- a/packages/mdc-drawer/util.js +++ b/packages/mdc-drawer/util.js @@ -23,7 +23,12 @@ import createFocusTrap from 'focus-trap'; -export function createFocusTrapInstance(surfaceEl, focusTrapFactory = createFocusTrap) { +/** + * @param {!Element} surfaceEl + * @param {!Function} focusTrapFactory + * @return {!FocusTrapInstance} + */ +function createFocusTrapInstance(surfaceEl, focusTrapFactory = createFocusTrap) { return focusTrapFactory(surfaceEl, { clickOutsideDeactivates: true, initialFocus: false, // Navigation drawer handles focusing on active nav item. @@ -31,3 +36,5 @@ export function createFocusTrapInstance(surfaceEl, focusTrapFactory = createFocu returnFocusOnDeactivate: false, // Navigation drawer handles restore focus. }); } + +export {createFocusTrapInstance}; diff --git a/test/unit/mdc-drawer/mdc-drawer.test.js b/test/unit/mdc-drawer/mdc-drawer.test.js index 09f6a5d46af..2d843d9406b 100644 --- a/test/unit/mdc-drawer/mdc-drawer.test.js +++ b/test/unit/mdc-drawer/mdc-drawer.test.js @@ -27,7 +27,6 @@ import domEvents from 'dom-events'; import td from 'testdouble'; import {MDCDrawer} from '../../../packages/mdc-drawer'; -import * as util from '../../../packages/mdc-drawer/util'; import {strings, cssClasses} from '../../../packages/mdc-drawer/constants'; import {MDCListFoundation} from '../../../packages/mdc-list'; import MDCDismissibleDrawerFoundation from '../../../packages/mdc-drawer/dismissible/foundation'; @@ -59,13 +58,21 @@ function setupTest(variantClass = cssClasses.DISMISSIBLE) { const root = getFixture(variantClass); const drawer = root.querySelector('.mdc-drawer'); const component = new MDCDrawer(drawer); - const MockFoundationCtor = td.constructor(MDCDismissibleDrawerFoundation); - const mockFoundation = new MockFoundationCtor(); - return {root, drawer, component, mockFoundation}; + return {root, drawer, component}; } -function hasClassMatcher(className) { - return td.matchers.argThat((el) => el.classList && el.classList.contains(className)); + +function setupTestWithMocks(variantClass = cssClasses.DISMISSIBLE) { + const root = getFixture(variantClass); + const drawer = root.querySelector('.mdc-drawer'); + const MockFoundationCtor = td.constructor(MDCDismissibleDrawerFoundation); + const mockFoundation = new MockFoundationCtor(); + const mockFocusTrapInstance = td.object({ + activate: () => {}, + deactivate: () => {}, + }); + const component = new MDCDrawer(drawer, mockFoundation, () => mockFocusTrapInstance); + return {root, drawer, component, mockFoundation, mockFocusTrapInstance}; } suite('MDCDrawer'); @@ -76,39 +83,34 @@ test('attachTo initializes and returns a MDCDrawer instance', () => { }); test('#get open calls foundation.isOpen', () => { - const {component} = setupTest(); - component.foundation_.isOpen = td.func(); + const {component, mockFoundation} = setupTestWithMocks(); component.open; - td.verify(component.foundation_.isOpen(), {times: 1}); + td.verify(mockFoundation.isOpen(), {times: 1}); }); test('#set open true calls foundation.open', () => { - const {component} = setupTest(); - component.foundation_.open = td.func(); + const {component, mockFoundation} = setupTestWithMocks(); component.open = true; - td.verify(component.foundation_.open(), {times: 1}); + td.verify(mockFoundation.open(), {times: 1}); }); test('#set open false calls foundation.close', () => { - const {component} = setupTest(); - component.foundation_.close = td.func(); + const {component, mockFoundation} = setupTestWithMocks(); component.open = false; - td.verify(component.foundation_.close(), {times: 1}); + td.verify(mockFoundation.close(), {times: 1}); }); test('keydown event calls foundation.handleKeydown method', () => { - const {component, drawer} = setupTest(); - component.foundation_.handleKeydown = td.func(); + const {drawer, mockFoundation} = setupTestWithMocks(); drawer.querySelector('.mdc-list-item').focus(); domEvents.emit(drawer, 'keydown'); - td.verify(component.foundation_.handleKeydown(td.matchers.isA(Object)), {times: 1}); + td.verify(mockFoundation.handleKeydown(td.matchers.isA(Object)), {times: 1}); }); test('transitionend event calls foundation.handleTransitionEnd method', () => { - const {component, drawer} = setupTest(); - component.foundation_.handleTransitionEnd = td.func(); + const {drawer, mockFoundation} = setupTestWithMocks(); domEvents.emit(drawer, 'transitionend'); - td.verify(component.foundation_.handleTransitionEnd(td.matchers.isA(Object)), {times: 1}); + td.verify(mockFoundation.handleTransitionEnd(td.matchers.isA(Object)), {times: 1}); }); test('component should throw error when invalid variant class name is used or no variant specified', () => { @@ -117,7 +119,7 @@ test('component should throw error when invalid variant class name is used or no }); test('#destroy removes keydown event listener', () => { - const {component, drawer, mockFoundation} = setupTest(); + const {component, drawer, mockFoundation} = setupTestWithMocks(); component.destroy(); drawer.querySelector('.mdc-list-item').focus(); domEvents.emit(drawer, 'keydown'); @@ -125,7 +127,7 @@ test('#destroy removes keydown event listener', () => { }); test('#destroy removes transitionend event listener', () => { - const {component, drawer, mockFoundation} = setupTest(); + const {component, drawer, mockFoundation} = setupTestWithMocks(); component.destroy(); domEvents.emit(drawer, 'transitionend'); @@ -218,41 +220,17 @@ test('adapter#restoreFocus focus is not restored if saveFocus never called', () }); test('adapter#trapFocus traps focus on root element', () => { - const {createFocusTrapInstance} = util; - util.createFocusTrapInstance = td.func('util.createFocusTrapInstance'); - - const fakeFocusTrapInstance = td.object({ - activate: () => {}, - deactivate: () => {}, - }); - td.when( - util.createFocusTrapInstance(hasClassMatcher('mdc-drawer')) - ).thenReturn(fakeFocusTrapInstance); - - const {component} = setupTest(cssClasses.MODAL); + const {component, mockFocusTrapInstance} = setupTestWithMocks(cssClasses.MODAL); component.getDefaultFoundation().adapter_.trapFocus(); - util.createFocusTrapInstance = createFocusTrapInstance; - td.verify(fakeFocusTrapInstance.activate()); + td.verify(mockFocusTrapInstance.activate()); }); test('adapter#releaseFocus releases focus on root element after trap focus', () => { - const {createFocusTrapInstance} = util; - util.createFocusTrapInstance = td.func('util.createFocusTrapInstance'); - - const fakeFocusTrapInstance = td.object({ - activate: () => {}, - deactivate: () => {}, - }); - td.when( - util.createFocusTrapInstance(hasClassMatcher('mdc-drawer')) - ).thenReturn(fakeFocusTrapInstance); - - const {component} = setupTest(cssClasses.MODAL); + const {component, mockFocusTrapInstance} = setupTestWithMocks(cssClasses.MODAL); component.getDefaultFoundation().adapter_.releaseFocus(); - util.createFocusTrapInstance = createFocusTrapInstance; - td.verify(fakeFocusTrapInstance.deactivate()); + td.verify(mockFocusTrapInstance.deactivate()); }); test('adapter#computeBoundingRect calls getBoundingClientRect() on root', () => {