From c53265955bbd4e894a98abe196764707d1a16ae8 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Sat, 30 Mar 2013 16:35:05 -0400 Subject: [PATCH] feat(tabs): Change directive name, add features * Rename 'tabs' directive to 'tabset', and 'pane' directive to 'tab'. The new syntax is more intuitive; The word pane does not obviously represent a subset of a tab group. (Closes #186) * Add 'tab-heading' directive, which is a child of a 'tab'. Allows HTML in tab headings. (Closes #124) * Add option for a 'select' attribute callback when a tab is selected. (Closes #141) * Tabs transclude to title elements instead of content elements. Now the ordering of tab titles is always correct. (Closes #153) BREAKING CHANGE: The 'tabs' directive has been renamed to 'tabset', and the 'pane' directive has been renamed to 'tab'. To migrate your code, follow the example below. Before: First Content {{apple.content}} After: First Content {{apple.content}} --- misc/demo/index.html | 12 +- src/tabs/docs/demo.html | 31 ++- src/tabs/docs/demo.js | 10 +- src/tabs/docs/readme.md | 6 +- src/tabs/tabs.js | 191 ++++++++++---- src/tabs/test/tabsSpec.js | 526 +++++++++++++++++++++++--------------- template/tabs/pane.html | 1 - template/tabs/tab.html | 3 + template/tabs/tabs.html | 8 - template/tabs/tabset.html | 12 + 10 files changed, 525 insertions(+), 275 deletions(-) delete mode 100644 template/tabs/pane.html create mode 100644 template/tabs/tab.html delete mode 100644 template/tabs/tabs.html create mode 100644 template/tabs/tabset.html diff --git a/misc/demo/index.html b/misc/demo/index.html index d6b358ff30..374acc5cb9 100644 --- a/misc/demo/index.html +++ b/misc/demo/index.html @@ -182,10 +182,14 @@

<%= module.displayName %>
- -
<%- module.docs.html %>
-
<%- module.docs.js %>
-
+ + +
<%- module.docs.html %>
+
+ +
<%- module.docs.js %>
+
+
diff --git a/src/tabs/docs/demo.html b/src/tabs/docs/demo.html index f16e31da19..ee8a435ee9 100644 --- a/src/tabs/docs/demo.html +++ b/src/tabs/docs/demo.html @@ -1,10 +1,23 @@
- - Static content - {{pane.content}} - -
- - -
-
\ No newline at end of file + Select a tab by setting active binding to true: +
+ + +

+ + Static content + + {{tab.content}} + + + + Select me for alert! + + I've got an HTML heading, and a select callback. Pretty cool! + + + diff --git a/src/tabs/docs/demo.js b/src/tabs/docs/demo.js index 59ce3accd2..509b746aa0 100644 --- a/src/tabs/docs/demo.js +++ b/src/tabs/docs/demo.js @@ -1,6 +1,12 @@ var TabsDemoCtrl = function ($scope) { - $scope.panes = [ + $scope.tabs = [ { title:"Dynamic Title 1", content:"Dynamic content 1" }, { title:"Dynamic Title 2", content:"Dynamic content 2" } ]; -}; \ No newline at end of file + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; +}; diff --git a/src/tabs/docs/readme.md b/src/tabs/docs/readme.md index 3c746c49eb..620b63045e 100644 --- a/src/tabs/docs/readme.md +++ b/src/tabs/docs/readme.md @@ -1 +1,5 @@ -AngularJS version of the tabs directive. \ No newline at end of file +AngularJS version of the tabs directive. + +Allows a `select` callback attribute, and `active` binding attribute. + +Allows either `heading` text-heading as an attribute, or a `` element inside as the heading. diff --git a/src/tabs/tabs.js b/src/tabs/tabs.js index b05d75f1b2..2d3d0a5c62 100644 --- a/src/tabs/tabs.js +++ b/src/tabs/tabs.js @@ -1,75 +1,168 @@ angular.module('ui.bootstrap.tabs', []) -.controller('TabsController', ['$scope', '$element', function($scope, $element) { - var panes = $scope.panes = []; - this.select = $scope.select = function selectPane(pane) { - angular.forEach(panes, function(pane) { - pane.selected = false; - }); - pane.selected = true; +.directive('tabs', function() { + return function() { + throw new Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md"); }; +}) - this.addPane = function addPane(pane) { - if (!panes.length) { - $scope.select(pane); +.controller('TabsetController', ['$scope', '$element', +function TabsetCtrl($scope, $element) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(tab) { + angular.forEach(tabs, function(tab) { + tab.active = false; + }); + tab.active = true; + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + if (tabs.length == 1) { + ctrl.select(tab); } - panes.push(pane); }; - this.removePane = function removePane(pane) { - var index = panes.indexOf(pane); - panes.splice(index, 1); - //Select a new pane if removed pane was selected - if (pane.selected && panes.length > 0) { - $scope.select(panes[index < panes.length ? index : index-1]); + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); } + tabs.splice(index, 1); }; }]) -.directive('tabs', function() { + +.directive('tabset', function() { return { restrict: 'EA', transclude: true, scope: {}, - controller: 'TabsController', - templateUrl: 'template/tabs/tabs.html', - replace: true + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html' }; }) -.directive('pane', ['$parse', function($parse) { + +.directive('tab', ['$parse', '$http', '$templateCache', '$compile', +function($parse, $http, $templateCache, $compile) { return { - require: '^tabs', + require: '^tabset', restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', transclude: true, - scope:{ - heading:'@' + scope: { + heading: '@', + onSelect: '&select' //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab }, - link: function(scope, element, attrs, tabsCtrl) { - var getSelected, setSelected; - scope.selected = false; - if (attrs.active) { - getSelected = $parse(attrs.active); - setSelected = getSelected.assign; - scope.$watch( - function watchSelected() {return getSelected(scope.$parent);}, - function updateSelected(value) {scope.selected = value;} - ); - scope.selected = getSelected ? getSelected(scope.$parent) : false; - } - scope.$watch('selected', function(selected) { - if(selected) { - tabsCtrl.select(scope); + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + var getActive, setActive; + scope.active = false; // default value + if (attrs.active) { + getActive = $parse(attrs.active); + setActive = getActive.assign; + scope.$parent.$watch(getActive, function updateActive(value) { + scope.active = !!value; + }); + } else { + setActive = getActive = angular.noop; } - if(setSelected) { - setSelected(scope.$parent, selected); + + scope.$watch('active', function(active) { + setActive(scope.$parent, active); + if (active) { + tabsetCtrl.select(scope); + scope.onSelect(); + } + }); + + scope.select = function() { + scope.active = true; + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + //If the tabset sets this tab to active, set the parent scope's active + //binding too. We do this so the watch for the parent's initial active + //value won't overwrite what is initially set by the tabset + if (scope.active) { + setActive(scope.$parent, true); + } + + //Transclude the collection of sibling elements. Use forEach to find + //the heading if it exists. We don't use a directive for tab-heading + //because it is problematic. Discussion @ http://git.io/MSNPwQ + transclude(scope.$parent, function(clone) { + //Look at every element in the clone collection. If it's tab-heading, + //mark it as that. If it's not tab-heading, mark it as tab contents + var contents = [], heading; + angular.forEach(clone, function(el) { + //See if it's a tab-heading attr or element directive + //First make sure it's a normal element, one that has a tagName + if (el.tagName && + (el.hasAttribute("tab-heading") || + el.hasAttribute("data-tab-heading") || + el.tagName.toLowerCase() == "tab-heading" || + el.tagName.toLowerCase() == "data-tab-heading" + )) { + heading = el; + } else { + contents.push(el); + } + }); + //Share what we found on the scope, so our tabHeadingTransclude and + //tabContentTransclude directives can find out what the heading and + //contents are. + if (heading) { + scope.headingElement = angular.element(heading); + } + scope.contentElement = angular.element(contents); + }); + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); } }); + } + }; +}]) - tabsCtrl.addPane(scope); - scope.$on('$destroy', function() { - tabsCtrl.removePane(scope); +.directive('tabContentTransclude', ['$parse', function($parse) { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs, tabsetCtrl) { + scope.$watch($parse(attrs.tabContentTransclude), function(tab) { + elm.html(''); + if (tab) { + elm.append(tab.contentElement); + } }); - }, - templateUrl: 'template/tabs/pane.html', - replace: true + } }; -}]); +}]) + +; + diff --git a/src/tabs/test/tabsSpec.js b/src/tabs/test/tabsSpec.js index 0c7ca733a9..74002e152f 100644 --- a/src/tabs/test/tabsSpec.js +++ b/src/tabs/test/tabsSpec.js @@ -1,257 +1,381 @@ describe('tabs', function() { - var elm, scope; - - // load the tabs code - beforeEach(module('ui.bootstrap.tabs')); - - // load the templates - beforeEach(module('template/tabs/tabs.html', 'template/tabs/pane.html')); - - beforeEach(inject(function($rootScope, $compile) { - // we might move this tpl into an html file as well... - elm = angular.element( - '
' + - '' + - '' + - 'first content is {{first}}' + - '' + - '' + - 'second content is {{second}}' + - '' + - '' + - '
'); - - scope = $rootScope; - $compile(elm)(scope); - scope.$digest(); - })); - - - it('should create clickable titles', inject(function($compile, $rootScope) { - var titles = elm.find('ul.nav-tabs li a'); - - expect(titles.length).toBe(2); - expect(titles.eq(0).text()).toBe('First Tab'); - expect(titles.eq(1).text()).toBe('Second Tab'); - })); + beforeEach(module('ui.bootstrap.tabs', 'template/tabs/tabset.html', 'template/tabs/tab.html')); + var elm, scope; + function titles() { + return elm.find('ul.nav-tabs li'); + } + function contents() { + return elm.find('div.tab-content div.tab-pane'); + } + + function expectTitles(titlesArray) { + var t = titles(); + expect(t.length).toEqual(titlesArray.length); + for (var i=0; i', + ' ', + ' ', + ' first content is {{first}}', + ' ', + ' ', + ' Second Tab {{second}}', + ' second content is {{second}}', + ' ', + ' ', + '' + ].join('\n'))(scope); + scope.$apply(); + return elm; + })); + + it('should create clickable titles', function() { + var t = titles(); + expect(t.length).toBe(2); + expect(t.find('a').eq(0).text()).toBe('First Tab 1'); + //It should put the tab-heading element into the 'a' title + expect(t.find('a').eq(1).children().is('tab-heading')).toBe(true); + expect(t.find('a').eq(1).children().html()).toBe('Second Tab 2'); + }); - it('should bind the content', function() { - var contents = elm.find('div.tab-content div.tab-pane'); + it('should bind tabs content and set first tab active', function() { + expectContents(['first content is 1', 'second content is 2']); + expect(titles().eq(0)).toHaveClass('active'); + expect(titles().eq(1)).not.toHaveClass('active'); + expect(scope.actives.one).toBe(true); + expect(scope.actives.two).toBe(false); + }); - expect(contents.length).toBe(2); - expect(contents.eq(0).text()).toBe('first content is '); - expect(contents.eq(1).text()).toBe('second content is '); + it('should change active on click', function() { + titles().eq(1).find('a').click(); + expect(contents().eq(1)).toHaveClass('active'); + expect(titles().eq(0)).not.toHaveClass('active'); + expect(titles().eq(1)).toHaveClass('active'); + expect(scope.actives.one).toBe(false); + expect(scope.actives.two).toBe(true); + }); - scope.$apply(function() { - scope.first = 123; - scope.second = 456; + it('should call select callback on select', function() { + titles().eq(1).find('a').click(); + expect(scope.selectSecond).toHaveBeenCalled(); + titles().eq(0).find('a').click(); + expect(scope.selectFirst).toHaveBeenCalled(); }); - expect(contents.eq(0).text()).toBe('first content is 123'); - expect(contents.eq(1).text()).toBe('second content is 456'); }); + describe('ng-repeat', function() { + + beforeEach(inject(function($compile, $rootScope) { + scope = $rootScope.$new(); + + function makeTab() { + return { + active: false, + select: jasmine.createSpy() + }; + } + scope.tabs = [ + makeTab(), makeTab(), makeTab(), makeTab() + ]; + elm = $compile([ + '', + ' ', + ' heading {{index}}', + ' content {{$index}}', + ' ', + '' + ].join('\n'))(scope); + scope.$apply(); + })); - it('should set active class on title', function() { - var titles = elm.find('ul.nav-tabs li'); - - expect(titles.eq(0)).toHaveClass('active'); - expect(titles.eq(1)).not.toHaveClass('active'); - }); - + function titles() { + return elm.find('ul.nav-tabs li'); + } + function contents() { + return elm.find('div.tab-content div.tab-pane'); + } - it('should set active class on content', function() { - var contents = elm.find('div.tab-content div.tab-pane'); + function expectTabActive(activeTab) { + var _titles = titles(); + angular.forEach(scope.tabs, function(tab, i) { + if (activeTab === tab) { + expect(tab.active).toBe(true); + //It should only call select ONCE for each select + expect(tab.select.callCount).toBe(1); + expect(_titles.eq(i)).toHaveClass('active'); + expect(contents().eq(i).text().trim()).toBe('content ' + i); + expect(contents().eq(i)).toHaveClass('active'); + } else { + expect(tab.active).toBe(false); + expect(_titles.eq(i)).not.toHaveClass('active'); + } + }); + } - expect(contents.eq(0)).toHaveClass('active'); - expect(contents.eq(1)).not.toHaveClass('active'); - }); + it('should make tab titles with first content and first active', function() { + expect(titles().length).toBe(scope.tabs.length); + expectTabActive(scope.tabs[0]); + }); - it('should change active and display on pane when title clicked', function() { - var titles = elm.find('ul.nav-tabs li'); - var contents = elm.find('div.tab-content div.tab-pane'); + it('should switch active when clicking', function() { + titles().eq(3).find('a').click(); + expectTabActive(scope.tabs[3]); + }); - // click the second tab - titles.eq(1).find('a').click(); + it('should switch active when setting active=true', function() { + scope.$apply('tabs[2].active = true'); + expectTabActive(scope.tabs[2]); + }); - // second title should be active - expect(titles.eq(0)).not.toHaveClass('active'); - expect(titles.eq(1)).toHaveClass('active'); + it('should deselect all when no tabs are active', function() { + angular.forEach(scope.tabs, function(t) { t.active = false; }); + scope.$apply(); + expectTabActive(null); + expect(contents().filter('.active').length).toBe(0); - // second content should be active and visible - expect(contents.eq(0)).not.toHaveClass('active'); - expect(contents.eq(0).css('display')).toBe('none'); - expect(contents.eq(1)).toHaveClass('active'); - expect(contents.eq(1).css('display')).not.toBe('none'); + scope.tabs[2].active = true; + scope.$apply(); + expectTabActive(scope.tabs[2]); + }); }); -}); + describe('advanced tab-heading element', function() { + beforeEach(inject(function($compile, $rootScope) { + scope = $rootScope.$new(); + scope.myHtml = "hello, there!"; + scope.value = true; + elm = $compile([ + '', + ' ', + ' ', + ' ', + ' ', + ' 1', + '
2
', + '
3
', + '
' + ].join('\n'))(scope); + scope.$apply(); + })); + + function heading() { + return elm.find('ul li a').children(); + } -describe('remote selection', function() { - var elm, scope; + it('should create a heading bound to myHtml', function() { + expect(heading().eq(0).html()).toBe("hello, there!"); + }); - // load the tabs code - beforeEach(module('ui.bootstrap.tabs')); - - // load the templates - beforeEach(module('template/tabs/tabs.html', 'template/tabs/pane.html')); - - beforeEach(inject(function($rootScope, $compile) { - // we might move this tpl into an html file as well... - elm = angular.element( - '
' + - '' + - '' + - '{{pane.content}}}' + - '' + - '' + - '
' - ); - scope = $rootScope; - scope.panes = [ - { title:"Dynamic Title 1", content:"Dynamic content 1", active:true}, - { title:"Dynamic Title 2", content:"Dynamic content 2" } - ]; - - $compile(elm)(scope); - scope.$digest(); - })); - - it('should handle select attribute when select/deselect', function() { - var titles = elm.find('ul.nav-tabs li'); - scope.$apply('panes[1].active=true'); - expect(titles.eq(1)).toHaveClass('active'); - - titles.eq(0).find('a').click(); - - expect(scope.panes[1].active).toBe(false); - }); + it('should hide and show the heading depending on value', function() { + expect(heading().eq(0).css('display')).not.toBe('none'); + scope.$apply('value = false'); + expect(heading().eq(0).css('display')).toBe('none'); + scope.$apply('value = true'); + expect(heading().eq(0).css('display')).not.toBe('none'); + }); - it('should select last active tab when multiple panes evaluate to active=true', function() { - var titles = elm.find('ul.nav-tabs li'); - scope.$apply('panes[0].active=true;panes[1].active=true'); - expect(titles.eq(1)).toHaveClass('active'); + it('should have a tab-heading no matter what syntax was used', function() { + expect(heading().eq(1).text()).toBe('1'); + expect(heading().eq(2).text()).toBe('2'); + expect(heading().eq(3).text()).toBe('3'); + }); + }); - it('should deselect all panes when all atrributes set to false', function() { - var titles = elm.find('ul.nav-tabs li'); - scope.$apply('panes[0].active=false'); - expect(titles.eq(0)).not.toHaveClass('active'); - expect(titles.eq(1)).not.toHaveClass('active'); + //Tests that http://git.io/lG6I9Q is fixed + describe('tab ordering', function() { + + beforeEach(inject(function($compile, $rootScope) { + scope = $rootScope.$new(); + scope.tabs = [ + { title:"Title 1", available:true }, + { title:"Title 2", available:true }, + { title:"Title 3", available:true } + ]; + elm = $compile([ + '', + ' ', + '
div that makes troubles
', + ' First Static', + '
another div that may do evil
', + ' some content', + ' ', + ' Mid Static', + ' a text node', + ' ', + ' yet another span that may do evil', + ' some content', + ' a text node', + ' yet another span that may do evil', + ' ', + ' Last Static', + ' a text node', + ' yet another span that may do evil', + ' ', + '
' + ].join('\n'))(scope); + + scope.tabIsAvailable = function(tab) { + return tab.available; + }; + })); + + it('should preserve correct ordering', function() { + function titles() { + return elm.find('ul.nav-tabs li a'); + } + scope.$apply(); + expect(titles().length).toBe(9); + scope.$apply('tabs[1].available=false'); + scope.$digest(); + expect(titles().length).toBe(7); + scope.$apply('tabs[0].available=false'); + scope.$digest(); + expect(titles().length).toBe(5); + scope.$apply('tabs[2].available=false'); + scope.$digest(); + expect(titles().length).toBe(3); + scope.$apply('tabs[0].available=true'); + scope.$digest(); + expect(titles().length).toBe(5); + scope.$apply('tabs[1].available=true'); + scope.$apply('tabs[2].available=true'); + scope.$digest(); + expect(titles().length).toBe(9); + expect(titles().eq(0).text().trim()).toBe("first"); + expect(titles().eq(1).text().trim()).toBe("Title 1"); + expect(titles().eq(2).text().trim()).toBe("Title 2"); + expect(titles().eq(3).text().trim()).toBe("Title 3"); + expect(titles().eq(4).text().trim()).toBe("mid"); + expect(titles().eq(5).text().trim()).toBe("Second Title 1"); + expect(titles().eq(6).text().trim()).toBe("Second Title 2"); + expect(titles().eq(7).text().trim()).toBe("Second Title 3"); + expect(titles().eq(8).text().trim()).toBe("last"); + }); }); -}); - -describe('tabs controller', function() { - var scope, ctrl; - beforeEach(module('ui.bootstrap.tabs')); - beforeEach(inject(function($controller, $rootScope) { - scope = $rootScope; + describe('tabset controller', function() { + function mockTab() { + return { active: false }; + } - // instantiate the controller stand-alone, without the directive - ctrl = $controller('TabsController', {$scope: scope, $element: null}); - })); + var ctrl; + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope; + //instantiate the controller stand-alone, without the directive + ctrl = $controller('TabsetController', {$scope: scope, $element: null}); + })); - describe('select', function() { + describe('select', function() { - it('should mark given pane selected', function() { - var pane = {}; + it('should mark given tab selected', function() { + var tab = mockTab(); - scope.select(pane); - expect(pane.selected).toBe(true); - }); + ctrl.select(tab); + expect(tab.active).toBe(true); + }); - it('should deselect other panes', function() { - var pane1 = {}, pane2 = {}, pane3 = {}; + it('should deselect other tabs', function() { + var tab1 = mockTab(), tab2 = mockTab(), tab3 = mockTab(); - ctrl.addPane(pane1); - ctrl.addPane(pane2); - ctrl.addPane(pane3); + ctrl.addTab(tab1); + ctrl.addTab(tab2); + ctrl.addTab(tab3); - scope.select(pane1); - expect(pane1.selected).toBe(true); - expect(pane2.selected).toBe(false); - expect(pane3.selected).toBe(false); + ctrl.select(tab1); + expect(tab1.active).toBe(true); + expect(tab2.active).toBe(false); + expect(tab3.active).toBe(false); - scope.select(pane2); - expect(pane1.selected).toBe(false); - expect(pane2.selected).toBe(true); - expect(pane3.selected).toBe(false); + ctrl.select(tab2); + expect(tab1.active).toBe(false); + expect(tab2.active).toBe(true); + expect(tab3.active).toBe(false); - scope.select(pane3); - expect(pane1.selected).toBe(false); - expect(pane2.selected).toBe(false); - expect(pane3.selected).toBe(true); + ctrl.select(tab3); + expect(tab1.active).toBe(false); + expect(tab2.active).toBe(false); + expect(tab3.active).toBe(true); + }); }); - }); - describe('addPane', function() { + describe('addTab', function() { - it('should append pane', function() { - var pane1 = {}, pane2 = {}; + it('should append tab', function() { + var tab1 = mockTab(), tab2 = mockTab(); - expect(scope.panes).toEqual([]); + expect(ctrl.tabs).toEqual([]); - ctrl.addPane(pane1); - expect(scope.panes).toEqual([pane1]); + ctrl.addTab(tab1); + expect(ctrl.tabs).toEqual([tab1]); - ctrl.addPane(pane2); - expect(scope.panes).toEqual([pane1, pane2]); - }); + ctrl.addTab(tab2); + expect(ctrl.tabs).toEqual([tab1, tab2]); + }); - it('should select the first one', function() { - var pane1 = {}, pane2 = {}; + it('should select the first one', function() { + var tab1 = mockTab(), tab2 = mockTab(); - ctrl.addPane(pane1); - expect(pane1.selected).toBe(true); + ctrl.addTab(tab1); + expect(tab1.active).toBe(true); - ctrl.addPane(pane2); - expect(pane1.selected).toBe(true); + ctrl.addTab(tab2); + expect(tab1.active).toBe(true); + }); }); }); -}); -describe('remove tabs', function() { + describe('remove', function() { - beforeEach(module("ui.bootstrap.tabs", "template/tabs/tabs.html", "template/tabs/pane.html")); + it('should remove title tabs when elements are destroyed and change selection', inject(function($controller, $compile, $rootScope) { + scope = $rootScope.$new(); + elm = $compile("Hellocontent {{i}}")(scope); + scope.$apply(); - it('should remove title panes when elements are destroyed and change selection', inject(function($controller, $compile, $rootScope) { - var scope = $rootScope; - var elm = $compile("Hellocontent {{i}}")(scope); - scope.$apply(); + expectTitles(['1']); + expectContents(['Hello']); - function titles() { - return elm.find('ul.nav-tabs li'); - } - function panes() { - return elm.find('div.tab-pane'); - } + scope.$apply('list = [1,2,3]'); + expectTitles(['1', 'tab 1', 'tab 2', 'tab 3']); + expectContents(['Hello', 'content 1', 'content 2', 'content 3']); - expect(titles().length).toBe(1); - expect(panes().length).toBe(1); - - scope.$apply('list = [1,2,3]'); - expect(titles().length).toBe(4); - expect(panes().length).toBe(4); - titles().find('a').eq(3).click(); - expect(panes().eq(3)).toHaveClass('active'); - expect(panes().eq(3).text().trim()).toBe('content 3'); - expect(titles().eq(3)).toHaveClass('active'); - expect(titles().eq(3).text().trim()).toBe('tab 3'); - - scope.$apply('list = [1,2]'); - expect(panes().length).toBe(3); - expect(titles().length).toBe(3); - expect(panes().eq(2)).toHaveClass('active'); - expect(panes().eq(2).text().trim()).toBe('content 2'); - expect(titles().eq(2)).toHaveClass('active'); - expect(titles().eq(2).text().trim()).toBe('tab 2'); - })); + titles().find('a').eq(3).click(); + expect(contents().eq(3)).toHaveClass('active'); + expect(titles().eq(3)).toHaveClass('active'); -}); + scope.$apply('list = [1,2]'); + expectTitles(['1', 'tab 1', 'tab 2']); + expectContents(['Hello', 'content 1', 'content 2']); + expect(titles().eq(2)).toHaveClass('active'); + expect(contents().eq(2)).toHaveClass('active'); + })); + }); +}); diff --git a/template/tabs/pane.html b/template/tabs/pane.html deleted file mode 100644 index 0783d02f0e..0000000000 --- a/template/tabs/pane.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/template/tabs/tab.html b/template/tabs/tab.html new file mode 100644 index 0000000000..d8b06452e5 --- /dev/null +++ b/template/tabs/tab.html @@ -0,0 +1,3 @@ +
  • + {{heading}} +
  • diff --git a/template/tabs/tabs.html b/template/tabs/tabs.html deleted file mode 100644 index 27bec3274e..0000000000 --- a/template/tabs/tabs.html +++ /dev/null @@ -1,8 +0,0 @@ -
    - -
    -
    diff --git a/template/tabs/tabset.html b/template/tabs/tabset.html new file mode 100644 index 0000000000..08d3212835 --- /dev/null +++ b/template/tabs/tabset.html @@ -0,0 +1,12 @@ + +
    + +
    +
    +
    +
    +