UNPKG

angular-material-npfixed

Version:

The Angular Material project is an implementation of Material Design in Angular.js. This project provides a set of reusable, well-tested, and accessible Material Design UI components. Angular Material is supported internally at Google by the Angular.js, M

580 lines (475 loc) 21.9 kB
describe('<md-tabs>', function () { beforeEach(module('material.components.tabs')); beforeEach(function () { jasmine.mockElementFocus(this); jasmine.addMatchers({ toBeActiveTab: function () { return { compare: function (actual, expected) { var fails = []; if (!actual.length) { fails.push('element not found'); } else { if (!actual.hasClass('md-active')) { fails.push('does not have active class'); } if (actual.attr('aria-selected') != 'true') { fails.push('aria-selected is not true'); } } var results = { pass: fails.length === 0 }; var negation = !results.pass ? "" : " not "; results.message = ""; results.message += "Expected '"; results.message += angular.mock.dump(actual); results.message += negation + "' to be the active tab. Failures: " + fails.join(', '); return results; } }; } }); }); function setup (template, scope) { var el; inject(function ($compile, $rootScope) { var newScope = $rootScope.$new(); for (var key in scope || {}) newScope[key] = scope[key]; el = $compile(template)(newScope); newScope.$apply(); }); return el; } function triggerKeydown (el, keyCode) { return el.triggerHandler({ type: 'keydown', keyCode: keyCode, preventDefault: angular.noop }); } describe('activating tabs', function () { it('should have `._md` class indicator', inject(function() { var tabs = setup( '<md-tabs> ' + ' <md-tab label="a">a</md-tab>' + ' <md-tab label="b">b</md-tab>' + '</md-tabs>' ); expect(tabs.find('md-tabs-content-wrapper').hasClass('_md')).toBe(true); })); it('should select first tab by default', function () { var tabs = setup( '<md-tabs> ' + ' <md-tab label="a">a</md-tab>' + ' <md-tab label="b">b</md-tab>' + '</md-tabs>' ); expect(tabs.find('md-tab-item').eq(0)).toBeActiveTab(); }); it('should select & focus tab on click', inject(function ($document, $rootScope) { var tabs = setup('<md-tabs>' + '<md-tab></md-tab>' + '<md-tab></md-tab>' + '<md-tab ng-disabled="true"></md-tab>' + '</md-tabs>'); var tabItems = tabs.find('md-tab-item'); tabs.find('md-tab-item').eq(1).triggerHandler('click'); $rootScope.$apply(); expect(tabItems.eq(1)).toBeActiveTab(); tabs.find('md-tab-item').eq(0).triggerHandler('click'); expect(tabItems.eq(0)).toBeActiveTab(); })); it('should focus tab on arrow if tab is enabled', inject(function ($document, $mdConstant, $timeout) { var tabs = setup('<md-tabs>' + '<md-tab></md-tab>' + '<md-tab ng-disabled="true"></md-tab>' + '<md-tab></md-tab>' + '</md-tabs>'); var tabItems = tabs.find('md-tab-item'); expect(tabItems.eq(0)).toBeActiveTab(); // Boundary case, do nothing triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.LEFT_ARROW); expect(tabItems.eq(0)).toBeActiveTab(); // Tab 0 should still be active, but tab 2 focused (skip tab 1 it's disabled) triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW); expect(tabItems.eq(0)).toBeActiveTab(); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.ENTER); expect(tabItems.eq(2)).toBeActiveTab(); // Boundary case, do nothing triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW); expect(tabItems.eq(2)).toBeActiveTab(); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.ENTER); expect(tabItems.eq(2)).toBeActiveTab(); // Skip tab 1 again, it's disabled triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.LEFT_ARROW); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.ENTER); expect(tabItems.eq(0)).toBeActiveTab(); })); it('should select tab on space or enter', inject(function ($mdConstant) { var tabs = setup('<md-tabs>' + '<md-tab></md-tab>' + '<md-tab></md-tab>' + '</md-tabs>'); var tabItems = tabs.find('md-tab-item'); tabs.find('md-tab-item').eq(0).triggerHandler('click'); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.ENTER); expect(tabItems.eq(1)).toBeActiveTab(); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.LEFT_ARROW); triggerKeydown(tabs.find('md-tabs-canvas').eq(0), $mdConstant.KEY_CODE.SPACE); expect(tabItems.eq(0)).toBeActiveTab(); })); it('should bind to selected', function () { var tabs = setup('<md-tabs md-selected="current">' + '<md-tab></md-tab>' + '<md-tab></md-tab>' + '<md-tab></md-tab>' + '</md-tabs>'); var tabItems = tabs.find('md-tab-item'); var dummyTabs = tabs.find('md-dummy-tab'); expect(tabItems.eq(0)).toBeActiveTab(); expect(tabs.scope().current).toBe(0); expect(dummyTabs.eq(0).attr('aria-selected')).toBe('true'); tabs.scope().$apply('current = 1'); expect(tabItems.eq(1)).toBeActiveTab(); expect(tabItems.eq(0).attr('aria-selected')).toBe('false'); expect(dummyTabs.eq(0).attr('aria-selected')).toBe('false'); expect(dummyTabs.eq(1).attr('aria-selected')).toBe('true'); tabItems.eq(2).triggerHandler('click'); expect(tabs.scope().current).toBe(2); }); it('disabling active tab', function () { var tabs = setup('<md-tabs>' + '<md-tab ng-disabled="disabled0"></md-tab>' + '<md-tab ng-disabled="disabled1"></md-tab>' + '</md-tabs>'); var tabItems = tabs.find('md-tab-item'); var dummyTabs = tabs.find('md-dummy-tab'); expect(tabItems.eq(0)).toBeActiveTab(); expect(dummyTabs.eq(0).attr('aria-selected')).toBe('true'); tabs.scope().$apply('disabled0 = true'); expect(tabItems.eq(1)).toBeActiveTab(); expect(tabItems.eq(0).attr('aria-disabled')).toBe('true'); expect(dummyTabs.eq(0).attr('aria-disabled')).toBe('true'); expect(tabItems.eq(1).attr('aria-disabled')).toBe('false'); expect(dummyTabs.eq(1).attr('aria-disabled')).toBe('false'); tabs.scope().$apply('disabled0 = false; disabled1 = true'); expect(tabItems.eq(0)).toBeActiveTab(); expect(tabItems.eq(0).attr('aria-disabled')).toBe('false'); expect(dummyTabs.eq(0).attr('aria-disabled')).toBe('false'); expect(tabItems.eq(1).attr('aria-disabled')).toBe('true'); expect(dummyTabs.eq(1).attr('aria-disabled')).toBe('true'); }); }); describe('tab label & content DOM', function () { it('should support both label types', function () { var tabs1 = setup('<md-tabs>' + '<md-tab label="super label"></md-tab>' + '</md-tabs>'); expect(tabs1.find('md-tab-item').text()).toBe('super label'); var tabs2 = setup('<md-tabs>' + '<md-tab><md-tab-label><b>super</b> label</md-tab-label></md-tab>' + '</md-tabs>'); expect(tabs2.find('md-tab-item').text()).toBe('super label'); }); it('should support content inside with each kind of label', function () { var tabs1 = setup('<md-tabs>' + '<md-tab label="label that!"><b>content</b> that!</md-tab>' + '</md-tabs>'); expect(tabs1.find('md-tab-item').text()).toBe('label that!'); expect(tabs1[ 0 ].querySelector('md-tab-content').textContent.trim()).toBe('content that!'); var tabs2 = setup('<md-tabs>\ <md-tab>\ <md-tab-label>label that!</md-tab-label>\ <md-tab-body><b>content</b> that!</md-tab-body>\ </md-tab>\ </md-tabs>'); expect(tabs1.find('md-tab-item').text()).toBe('label that!'); expect(tabs1[ 0 ].querySelector('md-tab-content').textContent.trim()).toBe('content that!'); }); it('updates pagination and ink styles when string labels change', function(done) { inject(function($rootScope, $timeout) { // Setup our initial label $rootScope.$apply('label = "Some Label"'); // Init our variables var template = '<md-tabs><md-tab label="{{label}}"></md-tab></md-tabs>'; var tabs = setup(template); var ctrl = tabs.controller('mdTabs'); // Flush the tabs controller timeout for initialization. $timeout.flush(); // After the first timeout the mutation observer should have been fired once, because // the initialization of the dummy tabs, already causes some mutations. // Use setTimeout to add our expectations to the end of the call stack, after the // MutationObservers have already fired setTimeout(function() { // Setup spies spyOn(ctrl, 'updatePagination'); spyOn(ctrl, 'updateInkBarStyles'); // Update the label to trigger a new update of the pagination and InkBar styles. $rootScope.$apply('label = "Another Label"'); // Use setTimeout to add our expectations to the end of the call stack, after the // MutationObservers have already fired setTimeout(function() { expect(ctrl.updatePagination).toHaveBeenCalledTimes(1); expect(ctrl.updateInkBarStyles).toHaveBeenCalledTimes(1); done(); }); }); }) }); it('updates pagination and ink styles when content label changes', function(done) { inject(function($rootScope, $timeout) { // Setup our initial label $rootScope.$apply('label = "Default Label"'); // Init our variables var template = '' + '<md-tabs>' + '<md-tab>' + '<md-tab-label>{{ label }}</md-tab-label>' + '</md-tab>' + '</md-tabs>'; var tabs = setup(template); var ctrl = tabs.controller('mdTabs'); // Flush the tabs controller timeout for initialization. $timeout.flush(); // After the first timeout the mutation observer should have been fired once, because // the initialization of the dummy tabs, already causes some mutations. // Use setTimeout to add our expectations to the end of the call stack, after the // MutationObservers have already fired setTimeout(function() { // Setup spies spyOn(ctrl, 'updatePagination'); spyOn(ctrl, 'updateInkBarStyles'); // Update the label to trigger a new update of the pagination and InkBar styles. $rootScope.$apply('label = "New Label"'); // Use setTimeout to add our expectations to the end of the call stack, after the // MutationObservers have already fired setTimeout(function() { expect(ctrl.updatePagination).toHaveBeenCalledTimes(1); expect(ctrl.updateInkBarStyles).toHaveBeenCalledTimes(1); done(); }); }); }) }); it('updates pagination and ink styles when HTML labels change', function(done) { inject(function($rootScope) { // Setup our initial label $rootScope.$apply('label = "Some Label"'); // Init our variables var template = '<md-tabs><md-tab><md-tab-label>{{label}}</md-tab-label></md-tab></md-tabs>'; var tabs = setup(template); var ctrl = tabs.controller('mdTabs'); // Setup spies spyOn(ctrl, 'updatePagination'); spyOn(ctrl, 'updateInkBarStyles'); // Change the label $rootScope.$apply('label="Another Label"'); // Use window.setTimeout to add our expectations to the end of the call stack, after the // MutationObservers have already fired window.setTimeout(function() { // Fire expectations expect(ctrl.updatePagination.calls.count()).toBe(1); expect(ctrl.updateInkBarStyles.calls.count()).toBe(1); done(); }); }); }); }); describe('aria', function () { var $timeout; beforeEach(inject(function(_$timeout_) { $timeout = _$timeout_; })); it('should link tab content to tabItem with auto-generated ids', function () { var tabs = setup('<md-tabs>' + '<md-tab label="label!">content!</md-tab>' + '</md-tabs>'); var tabItem = tabs.find('md-dummy-tab'); var tabContent = angular.element(tabs[ 0 ].querySelector('md-tab-content')); $timeout.flush(); expect(tabs.find('md-tabs-canvas').attr('role')).toBe('tablist'); expect(tabItem.attr('id')).toBeTruthy(); expect(tabItem.attr('aria-controls')).toBe(tabContent.attr('id')); expect(tabContent.attr('id')).toBeTruthy(); expect(tabContent.attr('role')).toBe('tabpanel'); expect(tabContent.attr('aria-labelledby')).toBe(tabItem.attr('id')); //Unique ids check expect(tabContent.attr('id')).not.toEqual(tabItem.attr('id')); }); it('should not assign role to dummy tabs', function () { var tabs = setup('<md-tabs>' + '<md-tab label="label!">content!</md-tab>' + '</md-tabs>'); var tabItem = tabs.find('md-dummy-tab'); expect(tabItem.attr('role')).toBeFalsy(); }); it('should assign role to visible tabs', function () { var tabs = setup('<md-tabs>' + '<md-tab label="label!">content!</md-tab>' + '</md-tabs>'); var tabItem = tabs.find('md-tab-item'); expect(tabItem.attr('role')).toBe('tab'); }); it('should not set `aria-controls` if the tab does not have content', function () { var tabs = setup( '<md-tabs>' + '<md-tab label="label!"></md-tab>' + '</md-tabs>' ); $timeout.flush(); expect(tabs.find('md-dummy-tab').attr('aria-controls')).toBeFalsy(); }); }); describe('<md-tab>', function () { it('should use its contents as the label if there is no label attribute or label/body tags', function () { var tab = setup('<md-tab>test</md-tab>'); expect(tab[ 0 ].tagName).toBe('MD-TAB'); expect(tab.find('md-tab-label').length).toBe(1); expect(tab.find('md-tab-label').text()).toBe('test'); expect(tab.find('md-tab-body').length).toBe(0); }); it('should use its contents as the body if there is a label attribute', function () { var tab = setup('<md-tab label="test">content</md-tab>'); expect(tab[ 0 ].tagName).toBe('MD-TAB'); expect(tab.find('md-tab-label').length).toBe(1); expect(tab.find('md-tab-body').length).toBe(1); expect(tab.find('md-tab-label').html()).toBe('test'); expect(tab.find('md-tab-body').html()).toBe('content'); }); it('should convert a label attribute to a label tag', function () { var tab = setup('<md-tab label="test"><md-tab-body>content</md-tab-body></md-tab>'); expect(tab[ 0 ].tagName).toBe('MD-TAB'); expect(tab.find('md-tab-label').length).toBe(1); expect(tab.find('md-tab-body').length).toBe(1); expect(tab.find('md-tab-label').html()).toBe('test'); expect(tab.find('md-tab-body').html()).toBe('content'); }); it('should not insert a body if there is no content', function () { var tab = setup('<md-tab>' + '<md-tab-label>test</md-tab-label>' + '</md-tab>'); expect(tab[ 0 ].tagName).toBe('MD-TAB'); expect(tab.find('md-tab-label').length).toBe(1); expect(tab.find('md-tab-label').text()).toBe('test'); expect(tab.find('md-tab-body').length).toBe(0); }); }); describe('internal scope', function () { it('should have the same internal scope as external', function () { var template = '\ <md-tabs md-selected="selectedTab">\ <md-tab label="a">\ <md-button ng-click="data = false">Set data to false</md-button>\ </md-tab>\ </md-tabs>\ '; var element = setup(template); var button = element.find('md-button'); expect(button[ 0 ].tagName).toBe('MD-BUTTON'); button.triggerHandler('click'); expect(element.scope().data).toBe(false); }); }); describe('no tab selected', function () { it('should allow cases where no tabs are selected', inject(function () { var template = '\ <md-tabs md-selected="selectedIndex">\ <md-tab label="a">tab content</md-tab>\ <md-tab label="b">tab content</md-tab>\ </md-tabs>\ '; var element = setup(template, { selectedIndex: -1 }); var scope = element.scope(); expect(scope.selectedIndex).toBe(-1); expect(element.find('md-tab-item').eq(0).hasClass('md-active')).toBe(false); expect(element.find('md-tab-item').eq(1).hasClass('md-active')).toBe(false); expect(element.find('md-tabs-content-wrapper').hasClass('ng-hide')).toBe(true); element.find('md-tab-item').eq(0).triggerHandler('click'); expect(element.find('md-tab-item').eq(0).hasClass('md-active')).toBe(true); expect(element.find('md-tab-item').eq(1).hasClass('md-active')).toBe(false); expect(scope.selectedIndex).toBe(0); element.find('md-tab-item').eq(1).triggerHandler('click'); expect(element.find('md-tab-item').eq(0).hasClass('md-active')).toBe(false); expect(element.find('md-tab-item').eq(1).hasClass('md-active')).toBe(true); expect(scope.selectedIndex).toBe(1); scope.$apply('selectedIndex = -1'); expect(scope.selectedIndex).toBe(-1); expect(element.find('md-tab-item').eq(0).hasClass('md-active')).toBe(false); expect(element.find('md-tab-item').eq(1).hasClass('md-active')).toBe(false); expect(element.find('md-tabs-content-wrapper').hasClass('ng-hide')).toBe(true); })); }); describe('nested tabs', function () { it('should properly nest tabs', inject(function () { var template = '' + '<md-tabs>' + ' <md-tab label="one">' + ' <md-tabs>' + ' <md-tab><md-tab-label>a</md-tab-label></md-tab>' + ' <md-tab><md-tab-label>b</md-tab-label></md-tab>' + ' <md-tab><md-tab-label>c</md-tab-label></md-tab>' + ' </md-tabs>' + ' </md-tab>' + ' <md-tab label="two">two</md-tab>' + '</md-tabs>'; var element = setup(template); // first item should be 'one' expect(element.find('md-tab-item').eq(0).text()).toBe('one'); // first item in nested tabs should be 'a' expect(element.find('md-tabs').find('md-tab-item').eq(0).text()).toBe('a'); })); }); describe('md-pagination-wrapper', function () { var template = '<md-tabs md-stretch-tabs="{{stretch}}">' + ' <md-tab label="label!">content!</md-tab>' + '</md-tabs>'; it('should have inline width if md-stretch-tabs="never"', inject(function ($timeout, $document) { var scope = { 'stretch': 'never' }; var element = setup(template, scope); // Appending to body is required for style checks angular.element($document.body).append(element); // $timeout.flush required to run nextTick inside init(); $timeout.flush(); expect(element.find('md-pagination-wrapper').attr('style').indexOf('width')).toBeGreaterThan(-1); element.remove(); })); it('should not have inline width if md-stretch-tabs="always"', inject(function ($timeout, $document) { var scope = { 'stretch': 'always' }; var element = setup(template, scope); // Appending to body is required for style checks angular.element($document.body).append(element); // $timeout.flush required to run nextTick inside init(); $timeout.flush(); expect(element.find('md-pagination-wrapper').attr('style').indexOf('width')).toBe(-1); element.remove(); })); }); describe('no element content', function() { it('should not add the `md-no-tab-content` class if the element has content', function() { var tabs = setup( '<md-tabs>' + '<md-tab label="label!">content!</md-tab>' + '</md-tabs>' ); expect(tabs).not.toHaveClass('md-no-tab-content'); }); it('should add the `md-no-tab-content` class if the element does not have content', function() { var tabs = setup( '<md-tabs>' + '<md-tab label="label!"></md-tab>' + '</md-tabs>' ); expect(tabs).toHaveClass('md-no-tab-content'); }); it('should trim before determining whether the element has content', function() { var tabs = setup( '<md-tabs>' + '<md-tab label="label!">\n\n\n</md-tab>' + '</md-tabs>' ); expect(tabs).toHaveClass('md-no-tab-content'); }); }); });