Skip to content

Commit

Permalink
fix(tab-bar): Move activateTab to adapter (#3394)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `getActiveTabIndex` adapter method renamed and `setActiveTab` adapter method added.
  • Loading branch information
bonniezhou committed Aug 23, 2018
1 parent f0ebfea commit 5007604
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 55 deletions.
5 changes: 3 additions & 2 deletions packages/mdc-tab-bar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@ Method Signature | Description
`getScrollContentWidth() => number` | Returns the width of the Tab Scroller's scroll content element.
`getOffsetWidth() => number` | Returns the offsetWidth of the root element.
`isRTL() => boolean` | Returns if the text direction is RTL.
`setActiveTab(index: number) => void` | Sets the tab at the given index to be activated.
`activateTabAtIndex(index: number, clientRect: ClientRect) => void` | Activates the Tab at the given index with the given clientRect.
`deactivateTabAtIndex(index) => void` | Deactivates the Tab at the given index.
`focusTabAtIndex(index: number) => void` | Focuses the Tab at the given index.
`getTabIndicatorClientRectAtIndex(index: number) => ClientRect` | Returns the client rect of the Tab at the given index.
`getTabDimensionsAtIndex(index) => MDCTabDimensions` | Returns the dimensions of the Tab at the given index.
`getTabListLength() => number` | Returns the number of child Tab components.
`getActiveTabIndex() => number` | Returns the index of the active Tab.
`getPreviousActiveTabIndex() => number` | Returns the index of the previously active Tab.
`getFocusedTabIndex() => number` | Returns the index of the focused Tab.
`getIndexOfTab(tab: MDCTab) => number` | Returns the index of the given Tab instance.
`notifyTabActivated(index: number) => void` | Emits the `MDCTabBar:activated` event.
Expand All @@ -129,7 +130,7 @@ Method Signature | Description

Method Signature | Description
--- | ---
`activateTab(index: number) => void` | Activates the Tab at the given index.
`activateTab(index: number) => void` | Activates the tab at the given index.
`handleKeyDown(evt: Event) => void` | Handles the logic for the `"keydown"` event.
`handleTabInteraction(evt: Event) => void` | Handles the logic for the `"MDCTab:interacted"` event.
`scrollIntoView(index: number) => void` | Scrolls the Tab at the given index into view.
10 changes: 8 additions & 2 deletions packages/mdc-tab-bar/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ class MDCTabBarAdapter {
*/
isRTL() {}

/**
* Sets the tab at the given index to be activated
* @param {number} index The index of the tab to activate
*/
setActiveTab(index) {}

/**
* Activates the tab at the given index with the given client rect
* @param {number} index The index of the tab to activate
Expand Down Expand Up @@ -115,10 +121,10 @@ class MDCTabBarAdapter {
getTabListLength() {}

/**
* Returns the index of the active tab
* Returns the index of the previously active tab
* @return {number}
*/
getActiveTabIndex() {}
getPreviousActiveTabIndex() {}

/**
* Returns the index of the focused tab
Expand Down
19 changes: 7 additions & 12 deletions packages/mdc-tab-bar/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {strings, numbers} from './constants';
import MDCTabBarAdapter from './adapter';

/* eslint-disable no-unused-vars */
import MDCTabFoundation from '@material/tab/foundation';
import {MDCTabDimensions} from '@material/tab/adapter';
/* eslint-enable no-unused-vars */

Expand Down Expand Up @@ -82,12 +81,13 @@ class MDCTabBarFoundation extends MDCFoundation {
getScrollContentWidth: () => {},
getOffsetWidth: () => {},
isRTL: () => {},
setActiveTab: () => {},
activateTabAtIndex: () => {},
deactivateTabAtIndex: () => {},
focusTabAtIndex: () => {},
getTabIndicatorClientRectAtIndex: () => {},
getTabDimensionsAtIndex: () => {},
getActiveTabIndex: () => {},
getPreviousActiveTabIndex: () => {},
getFocusedTabIndex: () => {},
getIndexOfTab: () => {},
getTabListLength: () => {},
Expand All @@ -105,11 +105,6 @@ class MDCTabBarFoundation extends MDCFoundation {
this.useAutomaticActivation_ = false;
}

init() {
const activeIndex = this.adapter_.getActiveTabIndex();
this.scrollIntoView(activeIndex);
}

/**
* Switches between automatic and manual activation modes.
* See https://www.w3.org/TR/wai-aria-practices/#tabpanel for examples.
Expand All @@ -124,7 +119,7 @@ class MDCTabBarFoundation extends MDCFoundation {
* @param {number} index
*/
activateTab(index) {
const previousActiveIndex = this.adapter_.getActiveTabIndex();
const previousActiveIndex = this.adapter_.getPreviousActiveTabIndex();
if (!this.indexIsInRange_(index) || index === previousActiveIndex) {
return;
}
Expand Down Expand Up @@ -159,13 +154,13 @@ class MDCTabBarFoundation extends MDCFoundation {
return;
}

const index = this.determineTargetFromKey_(this.adapter_.getActiveTabIndex(), key);
this.activateTab(index);
const index = this.determineTargetFromKey_(this.adapter_.getPreviousActiveTabIndex(), key);
this.adapter_.setActiveTab(index);
this.scrollIntoView(index);
} else {
const focusedTabIndex = this.adapter_.getFocusedTabIndex();
if (this.isActivationKey_(key)) {
this.activateTab(focusedTabIndex);
this.adapter_.setActiveTab(focusedTabIndex);
} else {
const index = this.determineTargetFromKey_(focusedTabIndex, key);
this.adapter_.focusTabAtIndex(index);
Expand All @@ -179,7 +174,7 @@ class MDCTabBarFoundation extends MDCFoundation {
* @param {!Event} evt
*/
handleTabInteraction(evt) {
this.activateTab(this.adapter_.getIndexOfTab(evt.detail.tab));
this.adapter_.setActiveTab(this.adapter_.getIndexOfTab(evt.detail.tab));
}

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/mdc-tab-bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ class MDCTabBar extends MDCComponent {

this.root_.addEventListener(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_);
this.root_.addEventListener('keydown', this.handleKeyDown_);

for (let i = 0; i < this.tabList_.length; i++) {
if (this.tabList_[i].active) {
this.scrollIntoView(i);
break;
}
}
}

destroy() {
Expand All @@ -118,12 +125,13 @@ class MDCTabBar extends MDCComponent {
getScrollContentWidth: () => this.tabScroller_.getScrollContentWidth(),
getOffsetWidth: () => this.root_.offsetWidth,
isRTL: () => window.getComputedStyle(this.root_).getPropertyValue('direction') === 'rtl',
setActiveTab: (index) => this.foundation_.activateTab(index),
activateTabAtIndex: (index, clientRect) => this.tabList_[index].activate(clientRect),
deactivateTabAtIndex: (index) => this.tabList_[index].deactivate(),
focusTabAtIndex: (index) => this.tabList_[index].focus(),
getTabIndicatorClientRectAtIndex: (index) => this.tabList_[index].computeIndicatorClientRect(),
getTabDimensionsAtIndex: (index) => this.tabList_[index].computeDimensions(),
getActiveTabIndex: () => {
getPreviousActiveTabIndex: () => {
for (let i = 0; i < this.tabList_.length; i++) {
if (this.tabList_[i].active) {
return i;
Expand Down
64 changes: 28 additions & 36 deletions test/unit/mdc-tab-bar/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,22 @@ test('exports numbers', () => {
test('defaultAdapter returns a complete adapter implementation', () => {
verifyDefaultAdapter(MDCTabBarFoundation, [
'scrollTo', 'incrementScroll', 'getScrollPosition', 'getScrollContentWidth',
'getOffsetWidth', 'isRTL',
'getOffsetWidth', 'isRTL', 'setActiveTab',
'activateTabAtIndex', 'deactivateTabAtIndex', 'focusTabAtIndex',
'getTabIndicatorClientRectAtIndex', 'getTabDimensionsAtIndex',
'getActiveTabIndex', 'getFocusedTabIndex', 'getIndexOfTab', 'getTabListLength',
'getPreviousActiveTabIndex', 'getFocusedTabIndex', 'getIndexOfTab', 'getTabListLength',
'notifyTabActivated',
]);
});

const setupTest = () => setupFoundationTest(MDCTabBarFoundation);

test('#init() scrolls the active tab into view', () => {
const {foundation, mockAdapter} = setupTest();
foundation.scrollIntoView = td.function();
td.when(mockAdapter.getActiveTabIndex()).thenReturn(99);
foundation.init();
td.verify(foundation.scrollIntoView(99), {times: 1});
});

const setupKeyDownTest = () => {
const {foundation, mockAdapter} = setupTest();
const activateTab = td.function();
foundation.setUseAutomaticActivation(false);
foundation.scrollIntoView = td.function(); // Avoid errors due to adapters being stubs
foundation.activateTab = activateTab;
return {activateTab, foundation, mockAdapter};
foundation.activateTab = td.function();
return {foundation, mockAdapter};
};

const mockKeyDownEvent = ({key, keyCode}) => {
Expand All @@ -84,9 +75,9 @@ const mockKeyDownEvent = ({key, keyCode}) => {
};

test('#handleTabInteraction() activates the tab', () => {
const {foundation, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
foundation.handleTabInteraction({detail: {}});
td.verify(activateTab(td.matchers.anything()), {times: 1});
td.verify(mockAdapter.setActiveTab(td.matchers.anything()), {times: 1});
});

test('#handleKeyDown() focuses the tab at the 0th index on home key press', () => {
Expand Down Expand Up @@ -209,62 +200,62 @@ test('#handleKeyDown() focuses the tab at the 0th index when the left arrow key
});

test('#handleKeyDown() activates the current focused tab on space/enter press w/o useAutomaticActivation', () => {
const {foundation, mockAdapter, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
const index = 2;
td.when(mockAdapter.getFocusedTabIndex()).thenReturn(index);
foundation.handleKeyDown(mockKeyDownEvent({key: MDCTabBarFoundation.strings.SPACE_KEY}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({keyCode: 32}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({key: MDCTabBarFoundation.strings.ENTER_KEY}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({keyCode: 13}).fakeEvent);

td.verify(activateTab(index), {times: 4});
td.verify(mockAdapter.setActiveTab(index), {times: 4});
});

test('#handleKeyDown() does nothing on space/enter press w/ useAutomaticActivation', () => {
const {foundation, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
foundation.setUseAutomaticActivation(true);
foundation.handleKeyDown(mockKeyDownEvent({key: MDCTabBarFoundation.strings.SPACE_KEY}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({keyCode: 32}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({key: MDCTabBarFoundation.strings.ENTER_KEY}).fakeEvent);
foundation.handleKeyDown(mockKeyDownEvent({keyCode: 13}).fakeEvent);

td.verify(activateTab(td.matchers.anything()), {times: 0});
td.verify(mockAdapter.setActiveTab(td.matchers.anything()), {times: 0});
});

test('#handleKeyDown() activates the tab at the 0th index on home key press w/ useAutomaticActivation', () => {
const {foundation, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
const {fakeEvent: fakeKeyEvent} = mockKeyDownEvent({key: MDCTabBarFoundation.strings.HOME_KEY});
const {fakeEvent: fakeKeyCodeEvent} = mockKeyDownEvent({keyCode: 36});
foundation.setUseAutomaticActivation(true);

foundation.handleKeyDown(fakeKeyEvent);
foundation.handleKeyDown(fakeKeyCodeEvent);
td.verify(activateTab(0), {times: 2});
td.verify(mockAdapter.setActiveTab(0), {times: 2});
});

test('#handleKeyDown() activates the tab at the N - 1 index on end key press w/ useAutomaticActivation', () => {
const {foundation, mockAdapter, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
const {fakeEvent: fakeKeyEvent} = mockKeyDownEvent({key: MDCTabBarFoundation.strings.END_KEY});
const {fakeEvent: fakeKeyCodeEvent} = mockKeyDownEvent({keyCode: 35});
foundation.setUseAutomaticActivation(true);
td.when(mockAdapter.getTabListLength()).thenReturn(13);

foundation.handleKeyDown(fakeKeyEvent);
foundation.handleKeyDown(fakeKeyCodeEvent);
td.verify(activateTab(12), {times: 2});
td.verify(mockAdapter.setActiveTab(12), {times: 2});
});

test('#handleKeyDown() activates the tab at the previous index on left arrow press w/ useAutomaticActivation', () => {
const {foundation, mockAdapter, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
const {fakeEvent: fakeKeyEvent} = mockKeyDownEvent({key: MDCTabBarFoundation.strings.ARROW_LEFT_KEY});
const {fakeEvent: fakeKeyCodeEvent} = mockKeyDownEvent({keyCode: 37});
foundation.setUseAutomaticActivation(true);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(2);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(2);
td.when(mockAdapter.getTabListLength()).thenReturn(13);

foundation.handleKeyDown(fakeKeyEvent);
foundation.handleKeyDown(fakeKeyCodeEvent);
td.verify(activateTab(1), {times: 2});
td.verify(mockAdapter.setActiveTab(1), {times: 2});
});

test('#handleKeyDown() prevents the default behavior for handled non-activation keys', () => {
Expand Down Expand Up @@ -328,12 +319,12 @@ test('#handleKeyDown() does not prevent the default behavior for unhandled keyCo
});

test('#handleKeyDown() does not activate a tab when an unhandled key is pressed', () => {
const {foundation, activateTab} = setupKeyDownTest();
const {foundation, mockAdapter} = setupKeyDownTest();
const {fakeEvent: fakeKeyEvent} = mockKeyDownEvent({key: 'Shift'});
const {fakeEvent: fakeKeyCodeEvent} = mockKeyDownEvent({keyCode: 16});
foundation.handleKeyDown(fakeKeyEvent);
foundation.handleKeyDown(fakeKeyCodeEvent);
td.verify(activateTab(), {times: 0});
td.verify(mockAdapter.setActiveTab(), {times: 0});
});

const setupActivateTabTest = () => {
Expand Down Expand Up @@ -361,7 +352,7 @@ test('#activateTab() does nothing if the index underflows the tab list', () => {

test('#activateTab() does nothing if the index is the same as the previous active index', () => {
const {foundation, mockAdapter} = setupActivateTabTest();
td.when(mockAdapter.getActiveTabIndex()).thenReturn(0);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(0);
td.when(mockAdapter.getTabListLength()).thenReturn(13);
foundation.activateTab(0);
td.verify(mockAdapter.deactivateTabAtIndex(td.matchers.isA(Number)), {times: 0});
Expand All @@ -372,23 +363,23 @@ test(`#activateTab() does not emit the ${MDCTabBarFoundation.strings.TAB_ACTIVAT
' is the currently active index', () => {
const {foundation, mockAdapter} = setupActivateTabTest();
td.when(mockAdapter.getTabListLength()).thenReturn(13);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(6);
foundation.activateTab(6);
td.verify(mockAdapter.notifyTabActivated(td.matchers.anything()), {times: 0});
});

test('#activateTab() deactivates the previously active tab', () => {
const {foundation, mockAdapter} = setupActivateTabTest();
td.when(mockAdapter.getTabListLength()).thenReturn(13);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(6);
foundation.activateTab(1);
td.verify(mockAdapter.deactivateTabAtIndex(6), {times: 1});
});

test('#activateTab() activates the newly active tab with the previously active tab\'s indicatorClientRect', () => {
const {foundation, mockAdapter} = setupActivateTabTest();
td.when(mockAdapter.getTabListLength()).thenReturn(13);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getTabIndicatorClientRectAtIndex(6)).thenReturn({
left: 22, right: 33,
});
Expand All @@ -399,7 +390,7 @@ test('#activateTab() activates the newly active tab with the previously active t
test('#activateTab() scrolls the new tab index into view', () => {
const {foundation, mockAdapter, scrollIntoView} = setupActivateTabTest();
td.when(mockAdapter.getTabListLength()).thenReturn(13);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getTabIndicatorClientRectAtIndex(6)).thenReturn({
left: 22, right: 33,
});
Expand All @@ -410,7 +401,7 @@ test('#activateTab() scrolls the new tab index into view', () => {
test(`#activateTab() emits the ${MDCTabBarFoundation.strings.TAB_ACTIVATED_EVENT} with the index of the tab`, () => {
const {foundation, mockAdapter} = setupActivateTabTest();
td.when(mockAdapter.getTabListLength()).thenReturn(13);
td.when(mockAdapter.getActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(6);
td.when(mockAdapter.getTabIndicatorClientRectAtIndex(6)).thenReturn({
left: 22, right: 33,
});
Expand All @@ -425,9 +416,10 @@ function setupScrollIntoViewTest({
scrollContentWidth = 1000,
scrollPosition = 0,
offsetWidth = 400,
tabDimensionsMap = {}} = {}) {
tabDimensionsMap = {},
} = {}) {
const {foundation, mockAdapter} = setupTest();
td.when(mockAdapter.getActiveTabIndex()).thenReturn(activeIndex);
td.when(mockAdapter.getPreviousActiveTabIndex()).thenReturn(activeIndex);
td.when(mockAdapter.getTabListLength()).thenReturn(tabListLength);
td.when(mockAdapter.getTabIndicatorClientRectAtIndex(td.matchers.isA(Number))).thenReturn(indicatorClientRect);
td.when(mockAdapter.getScrollPosition()).thenReturn(scrollPosition);
Expand Down
4 changes: 2 additions & 2 deletions test/unit/mdc-tab-bar/mdc-tab-bar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ test('#adapter.getTabDimensionsAtIndex calls computeDimensions on the tab at the
td.verify(component.tabList_[0].computeDimensions(), {times: 1});
});

test('#adapter.getActiveTabIndex returns the index of the active tab', () => {
test('#adapter.getPreviousActiveTabIndex returns the index of the active tab', () => {
const {component} = setupTest();
component.tabList_[1].active = true;
assert.strictEqual(component.getDefaultFoundation().adapter_.getActiveTabIndex(), 1);
assert.strictEqual(component.getDefaultFoundation().adapter_.getPreviousActiveTabIndex(), 1);
});

test('#adapter.getIndexOfTab returns the index of the given tab', () => {
Expand Down

0 comments on commit 5007604

Please sign in to comment.