describe('', function() { beforeEach(module('material.components.tabs')); beforeEach(function() { TestUtil.mockElementFocus(this); this.addMatchers({ toBeActiveTab: function(checkFocus) { var fails = []; var actual = this.actual; this.message = function() { return 'Expected ' + angular.mock.dump(actual) + (this.isNot ? ' not ' : ' ') + 'to be the active tab. Failures: ' + fails.join(', '); }; if (!actual.hasClass('active')) { fails.push('does not have active class'); } if (actual.attr('aria-selected') != 'true') { fails.push('aria-selected is not true'); } if (actual.attr('tabindex') != '0') { fails.push('tabindex is not 0'); } return fails.length === 0; } }); }); function setup(template) { var el; inject(function($compile, $rootScope) { el = $compile(template)($rootScope.$new()); $rootScope.$apply(); }); return el; } function triggerKeydown(el, keyCode) { return el.triggerHandler({ type: 'keydown', keyCode: keyCode }); } describe('activating tabs', function() { it('should select first tab by default', function() { var tabs = setup('' + '' + '' + ''); expect(tabs.find('md-tab').eq(0)).toBeActiveTab(); }); it('should select & focus tab on click', inject(function($document) { var tabs = setup('' + '' + '' + '' + ''); var tabItems = tabs.find('md-tab'); tabs.find('md-tab').eq(1).triggerHandler('click'); expect(tabItems.eq(1)).toBeActiveTab(); expect($document.activeElement).toBe(tabItems[1]); tabs.find('md-tab').eq(0).triggerHandler('click'); expect(tabItems.eq(0)).toBeActiveTab(); expect($document.activeElement).toBe(tabItems[0]); })); it('should focus tab on arrow if tab is enabled', inject(function($document, $mdConstant, $timeout) { var tabs = setup('' + '' + '' + '' + ''); var tabItems = tabs.find('md-tab'); expect(tabItems.eq(0)).toBeActiveTab(); // Boundary case, do nothing triggerKeydown(tabItems.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(tabItems.eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW); expect(tabItems.eq(0)).toBeActiveTab(); $timeout.flush(); expect($document.activeElement).toBe(tabItems[2]); // Boundary case, do nothing triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.RIGHT_ARROW); expect(tabItems.eq(0)).toBeActiveTab(); $timeout.flush(); expect($document.activeElement).toBe(tabItems[2]); // Skip tab 1 again, it's disabled triggerKeydown(tabItems.eq(2), $mdConstant.KEY_CODE.LEFT_ARROW); expect(tabItems.eq(0)).toBeActiveTab(); $timeout.flush(); expect($document.activeElement).toBe(tabItems[0]); })); it('should select tab on space or enter', inject(function($mdConstant) { var tabs = setup('' + '' + '' + ''); var tabItems = tabs.find('md-tab'); triggerKeydown(tabItems.eq(1), $mdConstant.KEY_CODE.ENTER); expect(tabItems.eq(1)).toBeActiveTab(); triggerKeydown(tabItems.eq(0), $mdConstant.KEY_CODE.SPACE); expect(tabItems.eq(0)).toBeActiveTab(); })); it('the active tab\'s content should always be connected', inject(function($timeout) { var tabs = setup('' + 'content1!' + 'content2!' + ''); var tabItems = tabs.find('md-tab'); var contents = angular.element(tabs[0].querySelectorAll('.md-tab-content')); $timeout.flush(); expect(contents.eq(0).scope().$$disconnected).toBeFalsy(); expect(contents.eq(1).scope().$$disconnected).toBeTruthy(); tabItems.eq(1).triggerHandler('click'); expect(contents.eq(0).scope().$$disconnected).toBeTruthy(); expect(contents.eq(1).scope().$$disconnected).toBeFalsy(); })); it('should bind to selected', function() { var tabs = setup('' + '' + '' + '' + ''); var tabItems = tabs.find('md-tab'); expect(tabItems.eq(0)).toBeActiveTab(); expect(tabs.scope().current).toBe(0); tabs.scope().$apply('current = 1'); expect(tabItems.eq(1)).toBeActiveTab(); tabItems.eq(2).triggerHandler('click'); expect(tabs.scope().current).toBe(2); }); it('should use active binding', function() { var tabs = setup('' + '' + '' + '' + ''); var tabItems = tabs.find('md-tab'); tabs.scope().$apply('active2 = true'); expect(tabItems.eq(2)).toBeActiveTab(); tabs.scope().$apply('active1 = true'); expect(tabItems.eq(1)).toBeActiveTab(); tabs.scope().$apply('active1 = false'); expect(tabItems.eq(1)).not.toBeActiveTab(); }); it('disabling active tab', function() { var tabs = setup('' + '' + '' + ''); var tabItems = tabs.find('md-tab'); expect(tabItems.eq(0)).toBeActiveTab(); tabs.scope().$apply('disabled0 = true'); expect(tabItems.eq(1)).toBeActiveTab(); expect(tabItems.eq(0).attr('aria-disabled')).toBe('true'); expect(tabItems.eq(1).attr('aria-disabled')).not.toBe('true'); tabs.scope().$apply('disabled0 = false; disabled1 = true'); expect(tabItems.eq(0)).toBeActiveTab(); expect(tabItems.eq(0).attr('aria-disabled')).not.toBe('true'); expect(tabItems.eq(1).attr('aria-disabled')).toBe('true'); }); it('swiping tabs', function() { var tabs = setup('' + '' + '' + ''); var tabItems = tabs.find('md-tab'); tabItems.eq(0).isolateScope().onSwipe({ type: 'swipeleft' }); expect(tabItems.eq(1)).toBeActiveTab(); tabItems.eq(1).isolateScope().onSwipe({ type: 'swipeleft' }); expect(tabItems.eq(1)).toBeActiveTab(); tabItems.eq(1).isolateScope().onSwipe({ type: 'swipeleft' }); expect(tabItems.eq(1)).toBeActiveTab(); tabItems.eq(1).isolateScope().onSwipe({ type: 'swiperight' }); expect(tabItems.eq(0)).toBeActiveTab(); tabItems.eq(0).isolateScope().onSwipe({ type: 'swiperight' }); expect(tabItems.eq(0)).toBeActiveTab(); }); }); describe('tab label & content DOM', function() { it('should support all 3 label types', function() { var tabs1 = setup('' + '' + ''); expect(tabs1.find('md-tab-label').html()).toBe('super label'); var tabs2 = setup('' + 'super label' + ''); expect(tabs2.find('md-tab-label').html()).toBe('super label'); var tabs3 = setup('' + 'super label' + ''); expect(tabs3.find('md-tab-label').html()).toBe('super label'); }); it('should support content inside with each kind of label', function() { var tabs1 = setup('' + 'content that!' + ''); expect(tabs1.find('md-tab-label').html()).toBe('label that!'); expect(tabs1[0].querySelector('.md-tabs-content .md-tab-content').innerHTML) .toBe('content that!'); var tabs2 = setup('' + 'label that!content that!' + ''); expect(tabs1.find('md-tab-label').html()).toBe('label that!'); expect(tabs1[0].querySelector('.md-tabs-content .md-tab-content').innerHTML) .toBe('content that!'); }); it('should connect content with child of the outside scope', function() { var tabs = setup('' + 'content!' + ''); var content = angular.element(tabs[0].querySelector('.md-tab-content')); expect(content.scope().$parent.$id).toBe(tabs.find('md-tab').scope().$id); }); }); describe('aria', function() { it('should link tab content to tabItem with auto-generated ids', function() { var tabs = setup('' + 'content!' + ''); var tabItem = tabs.find('md-tab'); var tabContent = angular.element(tabs[0].querySelector('.md-tab-content')); expect(tabs.attr('role')).toBe('tablist'); expect(tabItem.attr('id')).toBeTruthy(); expect(tabItem.attr('role')).toBe('tab'); 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')); }); }); });