From 69fda4e5267e8c66e3f3f232a10d160cc0ced338 Mon Sep 17 00:00:00 2001
From: Andy Joslin <andytjoslin@gmail.com>
Date: Thu, 13 Feb 2014 12:14:45 -0500
Subject: [PATCH] fix(tabs): broadcast tab.shown/tab.hidden to only child
 scopes

Addresses #588
---
 js/ext/angular/src/directive/ionicTabBar.js   | 178 +++----
 .../test/directive/ionicTabBar.unit.js        | 450 ++++++++++--------
 2 files changed, 356 insertions(+), 272 deletions(-)

diff --git a/js/ext/angular/src/directive/ionicTabBar.js b/js/ext/angular/src/directive/ionicTabBar.js
index 494d07a193b..8c16f667a90 100644
--- a/js/ext/angular/src/directive/ionicTabBar.js
+++ b/js/ext/angular/src/directive/ionicTabBar.js
@@ -13,89 +13,95 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml'])
   $ionicViewService.disableRegisterByTagName('tabs');
 }])
 
-.directive('tabs', ['$ionicViewService', function($ionicViewService) {
-  return {
-    restrict: 'E',
-    replace: true,
-    scope: true,
-    transclude: true,
-    controller: ['$scope', '$element', function($scope, $element) {
-      var _this = this;
-
-      $scope.tabCount = 0;
-      $scope.selectedIndex = -1;
-      $scope.$enableViewRegister = false;
-
-      angular.extend(this, ionic.controllers.TabBarController.prototype);
-
-
-      ionic.controllers.TabBarController.call(this, {
-        controllerChanged: function(oldC, oldI, newC, newI) {
-          $scope.controllerChanged && $scope.controllerChanged({
-            oldController: oldC,
-            oldIndex: oldI,
-            newController: newC,
-            newIndex: newI
-          });
-        },
-        tabBar: {
-          tryTabSelect: function() {},
-          setSelectedItem: function(index) {},
-          addItem: function(item) {}
-        }
+.controller('$ionicTabs', ['$scope', '$ionicViewService', function($scope, $ionicViewService) {
+  var _this = this;
+
+  $scope.tabCount = 0;
+  $scope.selectedIndex = -1;
+  $scope.$enableViewRegister = false;
+
+  angular.extend(this, ionic.controllers.TabBarController.prototype);
+
+  ionic.controllers.TabBarController.call(this, {
+    controllerChanged: function(oldC, oldI, newC, newI) {
+      $scope.controllerChanged && $scope.controllerChanged({
+        oldController: oldC,
+        oldIndex: oldI,
+        newController: newC,
+        newIndex: newI
       });
+    },
+    tabBar: {
+      tryTabSelect: function() {},
+      setSelectedItem: function(index) {},
+      addItem: function(item) {}
+    }
+  });
 
-      this.add = function(tabScope) {
-        tabScope.tabIndex = $scope.tabCount;
-        this.addController(tabScope);
-        if(tabScope.tabIndex === 0) {
-          this.select(0);
-        }
-        $scope.tabCount++;
-      };
+  this.add = function(tabScope) {
+    tabScope.tabIndex = $scope.tabCount;
+    this.addController(tabScope);
+    if(tabScope.tabIndex === 0) {
+      this.select(0);
+    }
+    $scope.tabCount++;
+  };
 
-      this.select = function(tabIndex, emitChange) {
-        if(tabIndex !== $scope.selectedIndex) {
-
-          $scope.selectedIndex = tabIndex;
-          $scope.activeAnimation = $scope.animation;
-          _this.selectController(tabIndex);
-
-          var viewData = {
-            type: 'tab',
-            typeIndex: tabIndex
-          };
-
-          for(var x=0; x<this.controllers.length; x++) {
-            if(tabIndex === this.controllers[x].tabIndex) {
-              viewData.title = this.controllers[x].title;
-              viewData.historyId = this.controllers[x].$historyId;
-              viewData.url = this.controllers[x].url;
-              viewData.uiSref = this.controllers[x].viewSref;
-              viewData.navViewName = this.controllers[x].navViewName;
-              viewData.hasNavView = this.controllers[x].hasNavView;
-              break;
-            }
-          }
-          if(emitChange) {
-            $scope.$emit('viewState.changeHistory', viewData);
-          }
-        } else if(emitChange) {
-          var currentView = $ionicViewService.getCurrentView();
-          if(currentView) {
-            $ionicViewService.goToHistoryRoot(currentView.historyId);
-          }
-        }
+  function controllerByTabIndex(tabIndex) {
+    for (var x=0; x<_this.controllers.length; x++) {
+      if (_this.controllers[x].tabIndex === tabIndex) {
+        return _this.controllers[x];
+      }
+    }
+  }
+
+  this.select = function(tabIndex, emitChange) {
+    if(tabIndex !== $scope.selectedIndex) {
+
+      $scope.selectedIndex = tabIndex;
+      $scope.activeAnimation = $scope.animation;
+      _this.selectController(tabIndex);
+
+      var viewData = {
+        type: 'tab',
+        typeIndex: tabIndex
       };
 
-      $scope.controllers = this.controllers;
+      var tabController = controllerByTabIndex(tabIndex);
+      if (tabController) {
+        viewData.title = tabController.title;
+        viewData.historyId = tabController.$historyId;
+        viewData.url = tabController.url;
+        viewData.uiSref = tabController.viewSref;
+        viewData.navViewName = tabController.navViewName;
+        viewData.hasNavView = tabController.hasNavView;
+      }
 
-      $scope.tabsController = this;
+      if(emitChange) {
+        $scope.$emit('viewState.changeHistory', viewData);
+      }
+    } else if(emitChange) {
+      var currentView = $ionicViewService.getCurrentView();
+      if (currentView) {
+        $ionicViewService.goToHistoryRoot(currentView.historyId);
+      }
+    }
+  };
 
-    }],
+  $scope.controllers = this.controllers;
 
-    template: '<div class="view"><tab-controller-bar></tab-controller-bar></div>',
+  $scope.tabsController = this;
+
+}])
 
+.directive('tabs', ['$ionicViewService', function($ionicViewService) {
+  return {
+    restrict: 'E',
+    replace: true,
+    scope: true,
+    transclude: true,
+    controller: '$ionicTabs',
+    template: '<div class="view"><tab-controller-bar></tab-controller-bar></div>',
     compile: function(element, attr, transclude, tabsCtrl) {
       return function link($scope, $element, $attr) {
 
@@ -173,9 +179,9 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml'])
         $scope.$watch(badgeGet, function(value) {
           $scope.badge = value;
         });
-        var badgeStyleGet = $interpolate(attr.badgeStyle || '');
-        $scope.$watch(badgeStyleGet, function(val) {
-          $scope.badgeStyle = val;
+
+        $attr.$observe('badgeStyle', function(value) {
+          $scope.badgeStyle = value;
         });
 
         var leftButtonsGet = $parse($attr.leftButtons);
@@ -193,17 +199,20 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml'])
 
         tabsCtrl.add($scope);
 
-        $scope.$watch('isVisible', function(value) {
+        function cleanupChild() {
           if(childElement) {
             childElement.remove();
             childElement = null;
-            $rootScope.$broadcast('tab.hidden');
           }
           if(childScope) {
             childScope.$destroy();
             childScope = null;
           }
-          if(value) {
+        }
+
+        $scope.$watch('isVisible', function(value) {
+          if (value) {
+            cleanupChild();
             childScope = $scope.$new();
             transclude(childScope, function(clone) {
               clone.addClass('pane');
@@ -211,7 +220,10 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml'])
               childElement = clone;
               $element.parent().append(childElement);
             });
-            $rootScope.$broadcast('tab.shown');
+            $scope.$broadcast('tab.shown');
+          } else if (childScope) {
+            $scope.$broadcast('tab.hidden');
+            cleanupChild();
           }
         });
 
@@ -229,13 +241,15 @@ angular.module('ionic.ui.tabs', ['ionic.service.view', 'ionic.ui.bindHtml'])
           }
         });
 
-        $rootScope.$on('$stateChangeSuccess', function(value){
+        var unregister = $rootScope.$on('$stateChangeSuccess', function(value){
           if( $ionicViewService.isCurrentStateNavView($scope.navViewName) &&
               $scope.tabIndex !== tabsCtrl.selectedIndex) {
             tabsCtrl.select($scope.tabIndex);
           }
         });
 
+        $scope.$on('$destroy', unregister);
+
       };
     }
   };
diff --git a/js/ext/angular/test/directive/ionicTabBar.unit.js b/js/ext/angular/test/directive/ionicTabBar.unit.js
index 7a341bccdcf..0d4e2b458db 100644
--- a/js/ext/angular/test/directive/ionicTabBar.unit.js
+++ b/js/ext/angular/test/directive/ionicTabBar.unit.js
@@ -1,236 +1,306 @@
-describe('Tab Bar Controller', function() {
-  var compile, element, scope, ctrl;
-
+describe('tabs', function() {
   beforeEach(module('ionic.ui.tabs'));
 
-  beforeEach(inject(function($compile, $rootScope, $controller) {
-    compile = $compile;
-    scope = $rootScope;
-    var e = compile('<tabs></tabs>')(scope);
-    ctrl = e.scope().tabsController;
-  }));
+  describe('$ionicTabs controller', function() {
 
-  it('Select item in controller works', function() {
-    // Verify no items selected
-    expect(ctrl.getSelectedControllerIndex()).toEqual(undefined);
+    var ctrl, scope;
+    beforeEach(inject(function($rootScope, $controller) {
+      scope = $rootScope.$new();
+      ctrl = $controller('$ionicTabs', {
+        $scope: scope
+      });
+    }));
 
-    // Try selecting beyond the bounds
-    ctrl.selectController(1);
-    expect(ctrl.getSelectedControllerIndex()).toEqual(undefined);
+    it('select should change getSelectedControllerIndex', function() {
+      // Verify no items selected
+      expect(ctrl.getSelectedControllerIndex()).toBeUndefined();
+      expect(scope.selectedIndex).toBe(-1);
 
-    // Add a controller
-    ctrl.add({
-      title: 'Cats',
-      icon: 'icon-kitty-kat'
-    });
+      // Try selecting beyond the bounds
+      ctrl.selectController(1);
+      expect(ctrl.getSelectedControllerIndex()).toBeUndefined();
+      expect(scope.selectedIndex).toBe(-1);
 
-    expect(ctrl.getSelectedControllerIndex()).toEqual(0);
+      // Add a controller
+      ctrl.add({
+        title: 'Cats',
+        icon: 'icon-kitty-kat'
+      });
 
-    ctrl.add({
-      title: 'Cats',
-      icon: 'icon-kitty-kat'
-    });
+      expect(ctrl.getSelectedControllerIndex()).toEqual(0);
+      expect(scope.selectedIndex).toBe(0);
 
-    expect(ctrl.getSelectedControllerIndex()).toEqual(0);
+      ctrl.add({
+        title: 'Cats',
+        icon: 'icon-kitty-kat'
+      });
 
-    ctrl.select(1);
+      expect(ctrl.getSelectedControllerIndex()).toEqual(0);
+      expect(scope.selectedIndex).toBe(0);
 
-    expect(ctrl.getSelectedControllerIndex()).toEqual(1);
-  });
+      ctrl.select(1);
 
-  it('Calls change callback', function() {
-    scope.onControllerChanged = function(oldC, oldI, newC, newI) {
-    };
-
-    // Add a controller
-    ctrl.add({
-      title: 'Cats',
-      icon: 'icon-kitty-kat'
-    });
-    ctrl.add({
-      title: 'Dogs',
-      icon: 'icon-rufus'
+      expect(ctrl.getSelectedControllerIndex()).toEqual(1);
+      expect(scope.selectedIndex).toBe(1);
     });
 
-    spyOn(ctrl, 'controllerChanged');
+    it('select should emit viewData if emit is passed in', function() {
+      ctrl.add({ title: 'foo', icon: 'icon' });
+      ctrl.add({ title: 'bar', icon: 'icon2' });
 
-    expect(ctrl.getSelectedControllerIndex()).toEqual(0);
-    ctrl.select(1);
+      var viewData;
+      spyOn(scope, '$emit').andCallFake(function(e, data) {
+        viewData = data;
+      });
 
-    expect(ctrl.controllerChanged).toHaveBeenCalled();
-  });
-});
+      ctrl.select(0);
+      expect(scope.$emit).not.toHaveBeenCalled();
 
-describe('Tabs directive', function() {
-  var compile, element, scope;
+      ctrl.select(1, true);
+      expect(scope.$emit).toHaveBeenCalledWith('viewState.changeHistory', jasmine.any(Object));
+      expect(viewData).toBeTruthy();
+      expect(viewData.type).toBe('tab');
+      expect(viewData.typeIndex).toBe(1);
+      expect(viewData.title).toBe('bar');
+    });
+    it('select should go to root if emit is true and selecting same tab index', inject(function($ionicViewService) {
+      ctrl.add({ title: 'foo', icon: 'icon' });
 
-  beforeEach(module('ionic.ui.tabs'));
+      spyOn($ionicViewService, 'goToHistoryRoot');
+      spyOn($ionicViewService, 'getCurrentView').andCallFake(function() {
+        return { historyId:'001' };
+      });
 
-  beforeEach(inject(function($compile, $rootScope) {
-    compile = $compile;
-    scope = $rootScope;
-  }));
+      expect(scope.selectedIndex).toBe(0);
+      //Emit != true
+      ctrl.select(0);
+      expect($ionicViewService.goToHistoryRoot).not.toHaveBeenCalled();
 
-  it('Has tab class', function() {
-    element = compile('<tabs></tabs>')(scope);
-    scope.$digest();
-    expect(element.find('.tabs').hasClass('tabs')).toBe(true);
-  });
+      ctrl.select(0, true);
+      expect($ionicViewService.goToHistoryRoot).toHaveBeenCalledWith('001');
+    }));
+    it('select should call change callback', function() {
+      scope.onControllerChanged = function(oldC, oldI, newC, newI) {
+      };
 
-  it('Has tab children', function() {
-    element = compile('<tabs></tabs>')(scope);
-    scope = element.scope();
-    scope.controllers = [
-      { title: 'Home', icon: 'icon-home' },
-      { title: 'Fun', icon: 'icon-fun' },
-      { title: 'Beer', icon: 'icon-beer' },
-    ];
-    scope.$digest();
-    expect(element.find('a').length).toBe(3);
-  });
+      // Add a controller
+      ctrl.add({ title: 'Cats', icon: 'icon-kitty-kat' });
+      ctrl.add({ title: 'Dogs', icon: 'icon-rufus' });
 
-  it('Has compiled children', function() {
-    element = compile('<tabs>' +
-      '<tab active="true" title="Item" icon="icon-default"></tab>' +
-      '<tab active="true" title="Item" icon="icon-default"></tab>' +
-    '</tabs>')(scope);
-    scope.$digest();
-    expect(element.find('a').length).toBe(2);
-  });
+      spyOn(ctrl, 'controllerChanged');
 
-  it('Sets style on child tabs', function() {
-    element = compile('<tabs tabs-type="tabs-positive" tabs-style="tabs-icon-bottom">' +
-      '<tab active="true" title="Item" icon="icon-default"></tab>' +
-      '<tab active="true" title="Item" icon="icon-default"></tab>' +
-    '</tabs>')(scope);
-    scope.$digest();
-    var tabs = element[0].querySelector('.tabs');
-    expect(angular.element(tabs).hasClass('tabs-positive')).toEqual(true);
-    expect(angular.element(tabs).hasClass('tabs-icon-bottom')).toEqual(true);
-  });
+      expect(ctrl.getSelectedControllerIndex()).toEqual(0);
+      ctrl.select(1);
+
+      expect(ctrl.controllerChanged).toHaveBeenCalled();
+    });
+    it('select should change activeAnimation=animation', function() {
+      // Add a controller
+      ctrl.add({ title: 'Cats', icon: 'icon-kitty-kat' });
+      ctrl.add({ title: 'Dogs', icon: 'icon-rufus' });
+
+      expect(scope.activeAnimation).toBeUndefined();
+      scope.animation = 'superfast';
+      ctrl.select(1);
+      expect(scope.activeAnimation).toBe('superfast');
+
+      scope.animation = 'woah';
+      ctrl.select(0);
+      expect(scope.activeAnimation).toBe('woah');
+    });
 
-  it('Has nav-view', function() {
-    element = compile('<tabs>' +
-      '<tab active="true" title="Item 1" href="#/page1"><nav-view name="name1"></nav-view></tab>' +
-      '<tab active="true" title="Item 2" href="/page2">content2</tab>' +
-    '</tabs>')(scope);
-    scope = element.scope();
-    scope.$digest();
-    expect(scope.tabCount).toEqual(2);
-    expect(scope.selectedIndex).toEqual(0);
-    expect(scope.controllers.length).toEqual(2);
-    expect(scope.controllers[0].hasNavView).toEqual(true);
-    expect(scope.controllers[0].navViewName).toEqual('name1');
-    expect(scope.controllers[0].url).toEqual('/page1');
-    expect(scope.controllers[1].hasNavView).toEqual(false);
-    expect(scope.controllers[1].url).toEqual('/page2');
   });
-});
 
-describe('Tab Item directive', function() {
-  var compile, element, scope, ctrl;
+  describe('tabs directive', function() {
+    var compile, scope, element;
+    beforeEach(inject(function($compile, $rootScope) {
+      compile = $compile;
+      scope = $rootScope;
+    }));
+
+    it('Has tab class', function() {
+      var element = compile('<tabs></tabs>')(scope);
+      scope.$digest();
+      expect(element.find('.tabs').hasClass('tabs')).toBe(true);
+    });
 
-  beforeEach(module('ionic.ui.tabs'));
+    it('Has tab children', function() {
+      element = compile('<tabs></tabs>')(scope);
+      scope = element.scope();
+      scope.controllers = [
+        { title: 'Home', icon: 'icon-home' },
+        { title: 'Fun', icon: 'icon-fun' },
+        { title: 'Beer', icon: 'icon-beer' },
+      ];
+      scope.$digest();
+      expect(element.find('a').length).toBe(3);
+    });
 
-  beforeEach(inject(function($compile, $rootScope, $document, $controller) {
-    compile = $compile;
-    scope = $rootScope;
+    it('Has compiled children', function() {
+      element = compile('<tabs>' +
+        '<tab active="true" title="Item" icon="icon-default"></tab>' +
+        '<tab active="true" title="Item" icon="icon-default"></tab>' +
+      '</tabs>')(scope);
+      scope.$digest();
+      expect(element.find('a').length).toBe(2);
+    });
 
-    scope.badgeValue = 3;
-    scope.badgeStyle = 'badge-assertive';
-    element = compile('<tabs>' +
-      '<tab title="Item" icon="icon-default" badge="badgeValue" badge-style="{{badgeStyle}}"></tab>' +
+    it('Sets style on child tabs', function() {
+      element = compile('<tabs tabs-type="tabs-positive" tabs-style="tabs-icon-bottom">' +
+        '<tab active="true" title="Item" icon="icon-default"></tab>' +
+        '<tab active="true" title="Item" icon="icon-default"></tab>' +
       '</tabs>')(scope);
-    scope.$digest();
-    $document[0].body.appendChild(element[0]);
-  }));
-
-  it('Title works', function() {
-    //The badge's text gets in the way of just doing .text() on the element itself, so exclude it
-    var notBadge = angular.element(element[0].querySelectorAll('a >:not(.badge)'));
-    expect(notBadge.text().trim()).toEqual('Item');
-  });
+      scope.$digest();
+      var tabs = element[0].querySelector('.tabs');
+      expect(angular.element(tabs).hasClass('tabs-positive')).toEqual(true);
+      expect(angular.element(tabs).hasClass('tabs-icon-bottom')).toEqual(true);
+    });
 
-  it('Default icon works', function() {
-    scope.$digest();
-    var i = element[0].querySelectorAll('i')[1];
-    expect(angular.element(i).hasClass('icon-default')).toEqual(true);
-  });
+    it('Has nav-view', function() {
+      element = compile('<tabs>' +
+        '<tab active="true" title="Item 1" href="#/page1"><nav-view name="name1"></nav-view></tab>' +
+        '<tab active="true" title="Item 2" href="/page2">content2</tab>' +
+      '</tabs>')(scope);
+      scope = element.scope();
+      scope.$digest();
+      expect(scope.tabCount).toEqual(2);
+      expect(scope.selectedIndex).toEqual(0);
+      expect(scope.controllers.length).toEqual(2);
+      expect(scope.controllers[0].hasNavView).toEqual(true);
+      expect(scope.controllers[0].navViewName).toEqual('name1');
+      expect(scope.controllers[0].url).toEqual('/page1');
+      expect(scope.controllers[1].hasNavView).toEqual(false);
+      expect(scope.controllers[1].url).toEqual('/page2');
+    });
 
-  it('Badge works', function() {
-    scope.$digest();
-    var i = element[0].querySelector('.badge');
-    expect(i.innerHTML).toEqual('3');
-    expect(i.className).toMatch('badge-assertive');
   });
 
-  it('Badge updates', function() {
-    scope.badgeValue = 10;
-    scope.$digest();
-    var i = element[0].querySelectorAll('i')[0];
-    expect(i.innerHTML).toEqual('10');
-  });
+  describe('tab-item Directive', function() {
+
+    var compile, element, scope, ctrl;
+    beforeEach(inject(function($compile, $rootScope, $document, $controller) {
+      compile = $compile;
+      scope = $rootScope.$new();
+
+      scope.badgeValue = 3;
+      scope.badgeStyleValue = 'badge-assertive';
+      element = compile('<tabs>' +
+        '<tab title="Item" icon="icon-default" badge="badgeValue" badge-style="{{badgeStyleValue}}"></tab>' +
+        '</tabs>')(scope);
+      scope.$digest();
+      $document[0].body.appendChild(element[0]);
+    }));
+
+    it('Title works', function() {
+      //The badge's text gets in the way of just doing .text() on the element itself, so exclude it
+      var notBadge = angular.element(element[0].querySelectorAll('a >:not(.badge)'));
+      expect(notBadge.text().trim()).toEqual('Item');
+    });
 
-  it('Click sets correct tab index', function() {
-    var a = element.find('a:eq(0)');
-    var itemScope = a.isolateScope();
-    //spyOn(a, 'click');
-    spyOn(itemScope, 'selectTab');
-    a.click();
-    expect(itemScope.selectTab).toHaveBeenCalled();
-  });
-});
+    it('Default icon works', function() {
+      scope.$digest();
+      var i = element[0].querySelectorAll('i')[1];
+      expect(angular.element(i).hasClass('icon-default')).toEqual(true);
+    });
 
-describe('Tab Controller Item directive', function() {
-  var compile, element, scope, ctrl;
+    it('Badge works', function() {
+      scope.$digest();
+      var i = element[0].querySelector('.badge');
+      expect(i.innerHTML).toEqual('3');
+      expect(i.className).toMatch('badge-assertive');
+      scope.$apply("badgeStyleValue = 'badge-danger'");
+      expect(i.className).toMatch('badge-danger');
+    });
 
-  beforeEach(module('ionic.ui.tabs'));
+    it('Badge updates', function() {
+      scope.badgeValue = 10;
+      scope.$digest();
+      var i = element[0].querySelectorAll('i')[0];
+      expect(i.innerHTML).toEqual('10');
+    });
 
-  beforeEach(inject(function($compile, $rootScope, $document, $controller) {
-    compile = $compile;
-    scope = $rootScope;
-
-    scope.badgeValue = 3;
-    scope.isActive = false;
-    element = compile('<tabs class="tabs">' +
-      '<tab-controller-item icon-title="Icon <b>title</b>" icon="icon-class" icon-on="icon-on-class" icon-off="icon-off-class" badge="badgeValue" badge-style="badgeStyle" active="isActive" index="0"></tab-controller-item>' +
-    '</tabs>')(scope);
-    scope.$digest();
-    $document[0].body.appendChild(element[0]);
-  }));
-
-  it('Icon title works as html', function() {
-    expect(element.find('a').find('span').html()).toEqual('Icon <b>title</b>');
+    it('Click sets correct tab index', function() {
+      var a = element.find('a:eq(0)');
+      var itemScope = a.isolateScope();
+      //spyOn(a, 'click');
+      spyOn(itemScope, 'selectTab');
+      a.click();
+      expect(itemScope.selectTab).toHaveBeenCalled();
+    });
   });
 
-  it('Icon classes works', function() {
-    var title = '';
-    var elements = element[0].querySelectorAll('.icon-class');
-    expect(elements.length).toEqual(1);
-    var elements = element[0].querySelectorAll('.icon-off-class');
-    expect(elements.length).toEqual(1);
+  describe('tab directive', function() {
+    var scope, tab;
+    beforeEach(inject(function($compile, $rootScope, $controller) {
+      var tabsScope = $rootScope.$new();
+      //Setup a fake tabs controller for our tab to use so we dont have to have a parent tabs directive (isolated test)
+      var ctrl = $controller('$ionicTabs', {
+        $scope: tabsScope
+      });
+
+      //Create an outer div that has a tabsController on it so tab thinks it's in a <tabs>
+      var element = angular.element('<div><tab><div class="my-content"></div></tab></div>');
+      element.data('$tabsController', ctrl);
+      $compile(element)(tabsScope)
+      tabsScope.$apply();
+
+      tab = element.find('tab');
+      scope = tab.scope();
+    }));
   });
 
-  it('Active switch works', function() {
-    var elements = element[0].querySelectorAll('.icon-on-class');
-    expect(elements.length).toEqual(0);
+  describe('tab-controller-item Directive', function() {
 
-    scope.isActive = true;
-    scope.$digest();
+    var compile, element, scope, ctrl;
+    beforeEach(inject(function($compile, $rootScope, $document, $controller) {
+      compile = $compile;
+      scope = $rootScope;
 
-    var elements = element[0].querySelectorAll('.icon-on-class');
-    expect(elements.length).toEqual(1);
-  });
+      scope.badgeValue = 3;
+      scope.isActive = false;
+      element = compile('<tabs class="tabs">' +
+        '<tab-controller-item icon-title="Icon <b>title</b>" icon="icon-class" icon-on="icon-on-class" icon-off="icon-off-class" badge="badgeValue" badge-style="badgeStyle" active="isActive" index="0"></tab-controller-item>' +
+      '</tabs>')(scope);
+      scope.$digest();
+      $document[0].body.appendChild(element[0]);
+    }));
 
-  it('Badge updates', function() {
-    scope.badgeValue = 10;
-    scope.badgeStyle = 'badge-assertive';
-    scope.$digest();
-    var i = element[0].querySelector('.badge');
-    expect(i.innerHTML).toEqual('10');
-    expect(i.className).toMatch('badge-assertive');
-    scope.$apply('badgeStyle = "badge-super"');
-    expect(i.className).toMatch('badge-super');
-  });
+    it('Icon title works as html', function() {
+      expect(element.find('a').find('span').html()).toEqual('Icon <b>title</b>');
+    });
+
+    it('Icon classes works', function() {
+      var title = '';
+      var elements = element[0].querySelectorAll('.icon-class');
+      expect(elements.length).toEqual(1);
+      var elements = element[0].querySelectorAll('.icon-off-class');
+      expect(elements.length).toEqual(1);
+    });
+
+    it('Active switch works', function() {
+      var elements = element[0].querySelectorAll('.icon-on-class');
+      expect(elements.length).toEqual(0);
+
+      scope.isActive = true;
+      scope.$digest();
+
+      var elements = element[0].querySelectorAll('.icon-on-class');
+      expect(elements.length).toEqual(1);
+    });
 
+    it('Badge updates', function() {
+      scope.badgeValue = 10;
+      scope.badgeStyle = 'badge-assertive';
+      scope.$digest();
+      var i = element[0].querySelector('.badge');
+      expect(i.innerHTML).toEqual('10');
+      expect(i.className).toMatch('badge-assertive');
+      scope.$apply('badgeStyle = "badge-super"');
+      expect(i.className).toMatch('badge-super');
+    });
+
+
+  });
 });
+
+