'
+ template: ''
};
});
@@ -2302,6 +2314,344 @@ angular.module('ionic.ui.toggle', [])
(function() {
'use strict';
+/**
+ * @description
+ * The NavController is a navigation stack View Controller modelled off of
+ * UINavigationController from Cocoa Touch. With the Nav Controller, you can
+ * "push" new "pages" on to the navigation stack, and then pop them off to go
+ * back. The NavController controls a navigation bar with a back button and title
+ * which updates as the pages switch.
+ *
+ * The NavController makes sure to not recycle scopes of old pages
+ * so that a pop will still show the same state that the user left.
+ *
+ * However, once a page is popped, its scope is destroyed and will have to be
+ * recreated then next time it is pushed.
+ *
+ */
+
+angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture'])
+
+/**
+ * Our Nav Bar directive which updates as the controller state changes.
+ */
+.directive('navBar', ['ViewService', '$rootScope', '$animate', '$compile',
+ function( ViewService, $rootScope, $animate, $compile) {
+
+ /**
+ * Perform an animation between one tab bar state and the next.
+ * Right now this just animates the titles.
+ */
+ var animate = function($scope, $element, oldTitle, data, cb) {
+ var title, nTitle, oTitle, titles = $element[0].querySelectorAll('.title');
+
+ var newTitle = data.title;
+ if(!oldTitle || oldTitle === newTitle) {
+ cb();
+ return;
+ }
+
+ // Clone the old title and add a new one so we can show two animating in and out
+ // add ng-leave and ng-enter during creation to prevent flickering when they are swapped during animation
+ title = angular.element(titles[0]);
+ oTitle = $compile('')($scope);
+ title.replaceWith(oTitle);
+ nTitle = $compile('')($scope);
+
+ var insert = $element[0].firstElementChild || null;
+
+ // Insert the new title
+ $animate.enter(nTitle, $element, insert && angular.element(insert), function() {
+ cb();
+ });
+
+ // Remove the old title
+ $animate.leave(angular.element(oTitle), function() {
+ });
+ };
+
+ return {
+ restrict: 'E',
+ replace: true,
+ scope: {
+ type: '@',
+ backButtonType: '@',
+ backButtonLabel: '@',
+ backButtonIcon: '@',
+ alignTitle: '@'
+ },
+ template: ''+//' ng-class="{invisible: !navController.navBar.isVisible}">' +
+ '
'
+ template: ''
};
});
diff --git a/js/ext/angular/src/directive/ionicViewState.js b/js/ext/angular/src/directive/ionicViewState.js
new file mode 100644
index 0000000000..30ca705b9f
--- /dev/null
+++ b/js/ext/angular/src/directive/ionicViewState.js
@@ -0,0 +1,338 @@
+(function() {
+'use strict';
+
+/**
+ * @description
+ * The NavController is a navigation stack View Controller modelled off of
+ * UINavigationController from Cocoa Touch. With the Nav Controller, you can
+ * "push" new "pages" on to the navigation stack, and then pop them off to go
+ * back. The NavController controls a navigation bar with a back button and title
+ * which updates as the pages switch.
+ *
+ * The NavController makes sure to not recycle scopes of old pages
+ * so that a pop will still show the same state that the user left.
+ *
+ * However, once a page is popped, its scope is destroyed and will have to be
+ * recreated then next time it is pushed.
+ *
+ */
+
+angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture'])
+
+/**
+ * Our Nav Bar directive which updates as the controller state changes.
+ */
+.directive('navBar', ['ViewService', '$rootScope', '$animate', '$compile',
+ function( ViewService, $rootScope, $animate, $compile) {
+
+ /**
+ * Perform an animation between one tab bar state and the next.
+ * Right now this just animates the titles.
+ */
+ var animate = function($scope, $element, oldTitle, data, cb) {
+ var title, nTitle, oTitle, titles = $element[0].querySelectorAll('.title');
+
+ var newTitle = data.title;
+ if(!oldTitle || oldTitle === newTitle) {
+ cb();
+ return;
+ }
+
+ // Clone the old title and add a new one so we can show two animating in and out
+ // add ng-leave and ng-enter during creation to prevent flickering when they are swapped during animation
+ title = angular.element(titles[0]);
+ oTitle = $compile('')($scope);
+ title.replaceWith(oTitle);
+ nTitle = $compile('')($scope);
+
+ var insert = $element[0].firstElementChild || null;
+
+ // Insert the new title
+ $animate.enter(nTitle, $element, insert && angular.element(insert), function() {
+ cb();
+ });
+
+ // Remove the old title
+ $animate.leave(angular.element(oTitle), function() {
+ });
+ };
+
+ return {
+ restrict: 'E',
+ replace: true,
+ scope: {
+ type: '@',
+ backButtonType: '@',
+ backButtonLabel: '@',
+ backButtonIcon: '@',
+ alignTitle: '@'
+ },
+ template: ''+//' ng-class="{invisible: !navController.navBar.isVisible}">' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '',
+ link: function($scope, $element, $attr, navCtrl) {
+
+ // Create the back button content and show/hide it based on scope settings
+ $scope.enableBackButton = true;
+ $scope.backButtonClass = $attr.backButtonType;
+ if($attr.backButtonIcon) {
+ $scope.backButtonClass += ' icon ' + $attr.backButtonIcon;
+ }
+
+ // Listen for changes in the stack cursor position to indicate whether a back
+ // button should be shown (this can still be disabled by the $scope.enableBackButton
+ $rootScope.$watch('$viewHistory.backView', function(backView) {
+ if(backView) {
+ var currentView = ViewService.getCurrentView();
+ if(currentView) {
+ if(backView.historyId === currentView.historyId) {
+ $scope.showBackButton = true;
+ return;
+ }
+ }
+ }
+ $scope.showBackButton = false;
+ });
+
+ // Store a reference to our nav controller
+ $scope.navController = navCtrl;
+
+ // Initialize our header bar view which will handle resizing and aligning our title labels
+ var hb = new ionic.views.HeaderBar({
+ el: $element[0],
+ alignTitle: $scope.alignTitle || 'center'
+ });
+ $scope.headerBarView = hb;
+
+ // Add the type of header bar class to this element
+ $element.addClass($scope.type);
+
+ var updateHeaderData = function(data) {
+ var oldTitle = $scope.currentTitle;
+ $scope.oldTitle = oldTitle;
+
+ if(typeof data.title !== 'undefined') {
+ $scope.currentTitle = data.title;
+ }
+
+ $scope.leftButtons = data.leftButtons;
+ $scope.rightButtons = data.rightButtons;
+
+ if(typeof data.hideBackButton !== 'undefined') {
+ $scope.enableBackButton = data.hideBackButton !== true;
+ }
+
+ if(data.animate !== false && $attr.animation && data.title && data.navDirection) {
+
+ $element[0].classList.add($attr.animation);
+ if(data.navDirection === 'back') {
+ $element[0].classList.add('reverse');
+ } else {
+ $element[0].classList.remove('reverse');
+ }
+
+ animate($scope, $element, oldTitle, data, function() {
+ hb.align();
+ });
+ } else {
+ hb.align();
+ }
+ };
+
+ $rootScope.$on('viewState.viewEnter', function(e, data) {
+ updateHeaderData(data);
+ });
+
+ // If a nav page changes the left or right buttons, update our scope vars
+ $scope.$parent.$on('viewState.leftButtonsChanged', function(e, data) {
+ $scope.leftButtons = data;
+ });
+ $scope.$parent.$on('viewState.rightButtonsChanged', function(e, data) {
+ $scope.rightButtons = data;
+ });
+
+ }
+ };
+}])
+
+
+.directive('view', ['ViewService', '$rootScope', '$animate',
+ function( ViewService, $rootScope, $animate) {
+ return {
+ restrict: 'EA',
+ priority: 1000,
+ scope: {
+ leftButtons: '=',
+ rightButtons: '=',
+ title: '=',
+ icon: '@',
+ iconOn: '@',
+ iconOff: '@',
+ type: '@',
+ alignTitle: '@',
+ hideBackButton: '@',
+ hideNavBar: '@',
+ animation: '@'
+ },
+
+ compile: function(tElement, tAttrs, transclude) {
+ tElement.addClass('pane');
+ tElement[0].removeAttribute('title');
+
+ return function link($scope, $element, $attr) {
+ // Should we hide a back button when this tab is shown
+ $scope.hideBackButton = $scope.$eval($scope.hideBackButton);
+
+ $scope.hideNavBar = $scope.$eval($scope.hideNavBar);
+
+ if($scope.hideBackButton === true) {
+ $scope.$emit('viewState.hideBackButton');
+ } else {
+ $scope.$emit('viewState.showBackButton');
+ }
+
+ // watch for changes in the left buttons
+ $scope.$watch('leftButtons', function(value) {
+ $scope.$emit('viewState.leftButtonsChanged', $scope.leftButtons);
+ });
+
+ $scope.$watch('rightButtons', function(val) {
+ $scope.$emit('viewState.rightButtonsChanged', $scope.rightButtons);
+ });
+
+ }
+ }
+ };
+}])
+
+
+.directive('viewBack', ['ViewService', function(ViewService) {
+ var goBack = function(e) {
+ var backView = ViewService.getBackView();
+ backView && backView.go();
+ e.alreadyHandled = true;
+ return false;
+ };
+
+ return {
+ restrict: 'AC',
+ link: function($scope, $element) {
+ $element.bind('click', goBack);
+ }
+ };
+}])
+
+
+.directive('uiView', ['ViewService', '$state', '$anchorScroll', '$compile', '$controller', '$animate',
+ function( ViewService, $state, $anchorScroll, $compile, $controller, $animate) {
+
+ var viewIsUpdating = false;
+ var animation;
+
+ var directive = {
+ restrict: 'ECA',
+ terminal: true,
+ priority: 2000,
+ transclude: true,
+
+ link: function(scope, $element, attr, ctrl, $transclude) {
+ var currentElement,
+ autoScrollExp = attr.autoscroll,
+ onloadExp = attr.onload || '',
+ viewLocals,
+ viewScope,
+ name = attr[directive.name] || attr.name || '',
+ parent = $element.parent().inheritedData('$uiView');
+
+ if (name.indexOf('@') < 0) name = name + '@' + (parent ? parent.state.name : '');
+ var view = { name: name, state: null, animation: null };
+ $element.data('$uiView', view);
+
+ var climbElement = $element[0];
+ while(!animation && climbElement) {
+ animation = climbElement.getAttribute('animation');
+ climbElement = climbElement.parentElement;
+ }
+
+ var eventHook = function() {
+ if (viewIsUpdating) return;
+ viewIsUpdating = true;
+
+ try { update(true); } catch (e) {
+ viewIsUpdating = false;
+ throw e;
+ }
+ viewIsUpdating = false;
+ };
+
+ scope.$on('$stateChangeSuccess', eventHook);
+ scope.$on('$viewContentLoading', eventHook);
+ update(false);
+
+ function update(doAnimation) {
+ var locals = $state.$current && $state.$current.locals[name],
+ template = (locals && locals.$template ? locals.$template.trim() : null);
+
+ if (locals === viewLocals) return; // nothing to do here, go about your business
+
+ var transitionOptions = {
+ parentElement: $element,
+ doAnimation: doAnimation,
+ leavingScope: viewScope,
+ leavingElement: currentElement,
+ navDirection: null
+ };
+
+ if (template) {
+ currentElement = angular.element(template);
+
+ var registerData = {};
+ if(currentElement[0].tagName !== 'TABS') {
+ registerData = ViewService.register(scope);
+ transitionOptions.navDirection = registerData.navDirection;
+ }
+
+ viewLocals = locals;
+ view.state = locals.$$state;
+
+ var link = $compile(currentElement),
+ current = $state.current;
+
+ viewScope = current.scope = scope.$new();
+
+ if (locals.$$controller) {
+ locals.$scope = viewScope;
+ var controller = $controller(locals.$$controller, locals);
+ if (current.controllerAs) {
+ viewScope[current.controllerAs] = controller;
+ }
+ currentElement.data('$ngControllerController', controller);
+ currentElement.children().data('$ngControllerController', controller);
+ }
+
+ link(viewScope);
+
+ viewScope.$emit('$viewContentLoaded');
+ viewScope.$eval(onloadExp);
+ viewScope.animation = animation;
+
+ transitionOptions.enteringScope = viewScope.$$childHead;
+ transitionOptions.enteringElement = currentElement;
+ }
+
+ ViewService.transition(transitionOptions);
+ }
+ }
+ };
+ return directive;
+}]);
+
+
+})();
\ No newline at end of file
diff --git a/js/ext/angular/src/service/ionicView.js b/js/ext/angular/src/service/ionicView.js
new file mode 100644
index 0000000000..8766f69087
--- /dev/null
+++ b/js/ext/angular/src/service/ionicView.js
@@ -0,0 +1,377 @@
+angular.module('ionic.service.view', ['ui.router'])
+
+
+.run( ['$rootScope', '$state', '$location', '$document',
+ function( $rootScope, $state, $location, $document) {
+
+ // init the variables that keep track of the view history
+ $rootScope.$viewHistory = {
+ histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
+ backView: null,
+ forwardView: null,
+ currentView: null
+ };
+
+ $rootScope.$on('viewState.changeHistory', function(e, data) {
+ if(!data) return;
+
+ var hist = (data.historyId ? $rootScope.$viewHistory.histories[ data.historyId ] : null );
+ if(hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
+ // the history they're going to already exists
+ // go to it's last view in its stack
+ var view = hist.stack[ hist.cursor ];
+ return view.go(data);
+ }
+
+ // this history does not have a URL, but it does have a uiSref
+ // figure out its URL from the uiSref
+ if(!data.url && data.uiSref) {
+ data.url = $state.href(data.uiSref);
+ }
+
+ if(data.url) {
+ // don't let it start with a #, messes with $location.url()
+ if(data.url.indexOf('#') === 0) {
+ data.url = data.url.replace('#', '');
+ }
+ if(data.url !== $location.url()) {
+ // we've got a good URL, ready GO!
+ $location.url(data.url);
+ }
+ }
+ });
+
+ // Set the document title when a new view is shown
+ $rootScope.$on('viewState.viewEnter', function(e, data) {
+ if(data && data.title) {
+ $document[0].title = data.title;
+ }
+ });
+
+}])
+
+.factory('ViewService', ['$rootScope', '$state', '$location', '$window', '$injector',
+ function( $rootScope, $state, $location, $window, $injector) {
+ var $animate = $injector.has('$animate') ? $injector.get('$animate') : false;
+
+ var View = function(){};
+ View.prototype.initialize = function(data) {
+ if(data) {
+ for(var name in data) this[name] = data[name];
+ return this;
+ }
+ return null;
+ };
+ View.prototype.go = function(opts) {
+ if(this.url && this.url !== $location.url() && (!opts || opts.enableUrlChange !== false)) {
+
+ if($rootScope.$viewHistory.backView === this) {
+ return $window.history.go(-1);
+ } else if($rootScope.$viewHistory.forwardView === this) {
+ return $window.history.go(1);
+ }
+
+ return $location.url(this.url);
+ }
+
+ if(this.stateName) {
+ return $state.go(this.stateName, this.stateParams);
+ }
+
+ return null;
+ };
+ View.prototype.destory = function() {
+ if(this.scope) {
+ this.scope.destory && this.scope.destory();
+ this.scope = null;
+ }
+ };
+
+ function createViewId(stateId) {
+ return ('_' + stateId + '_' + Math.round(Math.random() * 99999999)).replace(/\./g, '_');
+ }
+
+ return {
+
+ register: function(containerScope) {
+ var viewHistory = $rootScope.$viewHistory,
+ currentStateId = this.getCurrentStateId(),
+ hist = this._getHistory(containerScope),
+ currentView = viewHistory.currentView,
+ backView = viewHistory.backView,
+ forwardView = viewHistory.forwardView,
+ rsp = {
+ viewId: null,
+ navAction: null,
+ navDirection: null,
+ historyId: hist.historyId
+ };
+
+ if(currentView &&
+ currentView.stateId === currentStateId &&
+ currentView.historyId === hist.historyId) {
+ // do nothing if its the same stateId in the same history
+ rsp.navAction = 'noChange';
+ return rsp;
+ }
+
+ if(backView && backView.stateId === currentStateId) {
+ // they went back one, set the old current view as a forward view
+ rsp.viewId = backView.viewId;
+ rsp.navAction = 'moveBack';
+ if(backView.historyId === currentView.historyId) {
+ // went back in the same history
+ rsp.navDirection = 'back';
+ }
+
+ } else if(forwardView && forwardView.stateId === currentStateId) {
+ // they went to the forward one, set the forward view to no longer a forward view
+ rsp.viewId = forwardView.viewId;
+ rsp.navAction = 'moveForward';
+ if(forwardView.historyId === currentView.historyId) {
+ rsp.navDirection = 'forward';
+ }
+
+ var parentHistory = this._getParentHistoryObj(containerScope);
+ if(forwardView.historyId && parentHistory.scope) {
+ // if a history has already been created by the forward view then make sure it stays the same
+ parentHistory.scope.$historyId = forwardView.historyId;
+ rsp.historyId = forwardView.historyId;
+ }
+
+ } else {
+
+ // set a new unique viewId
+ rsp.viewId = createViewId(currentStateId);
+
+ if(currentView) {
+ // set the forward view if there is a current view (ie: if its not the first view)
+ currentView.forwardViewId = rsp.viewId;
+
+ // its only moving forward if its in the same history
+ if(hist.historyId === currentView.historyId) {
+ rsp.navDirection = 'forward';
+ }
+ rsp.navAction = 'newView';
+
+ // check if there is a new forward view
+ if(forwardView && currentView.stateId !== forwardView.stateId) {
+ // they navigated to a new view but the stack already has a forward view
+ // since its a new view remove any forwards that existed
+ var forwardsHistory = this._getView(forwardView.historyId);
+ if(forwardsHistory) {
+ // the forward has a history
+ for(var x=forwardsHistory.stack.length - 1; x >= forwardView.index; x--) {
+ // starting from the end destory all forwards in this history from this point
+ forwardsHistory.stack[x].destory();
+ forwardsHistory.stack.splice(x);
+ }
+ }
+ }
+
+ } else {
+ // there's no current view, so this must be the initial view
+ rsp.navAction = 'initialView';
+ }
+
+ // add the new view to the stack
+ viewHistory.histories[rsp.viewId] = this.createView({
+ viewId: rsp.viewId,
+ index: hist.stack.length,
+ historyId: hist.historyId,
+ backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
+ forwardViewId: null,
+ stateId: currentStateId,
+ stateName: this.getCurrentStateName(),
+ stateParams: this.getCurrentStateParams(),
+ url: $location.url()
+ });
+
+ // add the new view to this history's stack
+ hist.stack.push(viewHistory.histories[rsp.viewId]);
+ }
+
+ viewHistory.currentView = this._getView(rsp.viewId);
+ viewHistory.backView = this._getBackView(viewHistory.currentView);
+ viewHistory.forwardView = this._getForwardView(viewHistory.currentView);
+
+ hist.cursor = viewHistory.currentView.index;
+
+ return rsp;
+ },
+
+ registerHistory: function(scope) {
+ scope.$historyId = 'h' + Math.round(Math.random() * 99999999999);
+ },
+
+ createView: function(data) {
+ var newView = new View();
+ return newView.initialize(data);
+ },
+
+ getCurrentView: function() {
+ return $rootScope.$viewHistory.currentView;
+ },
+
+ getBackView: function() {
+ return $rootScope.$viewHistory.backView;
+ },
+
+ getForwardView: function() {
+ return $rootScope.$viewHistory.forwardView;
+ },
+
+ getNavDirection: function() {
+ return $rootScope.$viewHistory.navDirection;
+ },
+
+ getCurrentStateName: function() {
+ return ($state && $state.current ? $state.current.name : null);
+ },
+
+ isCurrentStateUiView: function(uiView) {
+ return ($state &&
+ $state.current &&
+ $state.current.views &&
+ $state.current.views[uiView] ? true : false);
+ },
+
+ getCurrentStateParams: function() {
+ var rtn;
+ if ($state && $state.params) {
+ for(var key in $state.params) {
+ if($state.params.hasOwnProperty(key)) {
+ rtn = rtn || {};
+ rtn[key] = $state.params[key];
+ }
+ }
+ }
+ return rtn;
+ },
+
+ getCurrentStateId: function() {
+ var id;
+ if($state && $state.current && $state.current.name) {
+ id = $state.current.name;
+ if($state.params) {
+ for(var key in $state.params) {
+ if($state.params.hasOwnProperty(key) && $state.params[key]) {
+ id += "_" + key + "=" + $state.params[key];
+ }
+ }
+ }
+ return id;
+ }
+ // if something goes wrong make sure its got a unique stateId
+ return 'r' + Math.round(Math.random() * 9999999);
+ },
+
+ _getView: function(viewId) {
+ return (viewId ? $rootScope.$viewHistory.histories[ viewId ] : null );
+ },
+
+ _getBackView: function(view) {
+ return (view ? this._getView(view.backViewId) : null );
+ },
+
+ _getForwardView: function(view) {
+ return (view ? this._getView(view.forwardViewId) : null );
+ },
+
+ _getHistory: function(scope) {
+ var histObj = this._getParentHistoryObj(scope);
+
+ if( !$rootScope.$viewHistory.histories[ histObj.historyId ] ) {
+ // this history object exists in parent scope, but doesn't
+ // exist in the history data yet
+ $rootScope.$viewHistory.histories[ histObj.historyId ] = {
+ historyId: histObj.historyId,
+ parentHistoryId: this._getParentHistoryObj(histObj.scope.$parent).historyId,
+ stack: [],
+ cursor: -1
+ };
+ }
+
+ return $rootScope.$viewHistory.histories[ histObj.historyId ];
+ },
+
+ _getParentHistoryObj: function(scope) {
+ var parentScope = scope;
+ while(parentScope) {
+ if(parentScope.hasOwnProperty('$historyId')) {
+ // this parent scope has a historyId
+ return { historyId: parentScope.$historyId, scope: parentScope };
+ }
+ // nothing found keep climbing up
+ parentScope = parentScope.$parent;
+ }
+ // no history for for the parent, use the root
+ return { historyId: 'root', scope: $rootScope };
+ },
+
+ transition: function(opts) {
+ if(!opts || !opts.enteringElement) return;
+
+ if (opts.leavingScope) {
+ opts.leavingScope.$destroy();
+ opts.leavingScope = null;
+ }
+
+ // use the directive's animation attribute first
+ // if it doesn't exist, then use the given animation
+ var animationClass = opts.animation || getAnimationClass();
+
+ if($animate && animationClass && opts.doAnimation !== false && opts.navDirection) {
+ // set the animation we're gonna use
+ this.setAnimationClass(opts.parentElement, animationClass, opts.navDirection);
+
+ // start the animations
+ if(opts.leavingElement) {
+ $animate.leave(opts.leavingElement, function() {
+ console.log('leave complete')
+ });
+ }
+ $animate.enter(opts.enteringElement, opts.parentElement);
+
+ } else {
+ // no animation, just plain ol' add/remove DOM elements
+ if(opts.leavingElement) {
+ opts.leavingElement.remove();
+ }
+ opts.parentElement.append(opts.enteringElement);
+ }
+
+ $rootScope.$broadcast('viewState.viewEnter', {
+ title: (opts.enteringScope ? opts.enteringScope.title : null),
+ navDirection: (opts.navDirection ? opts.navDirection : null)
+ });
+
+ function getAnimationClass(){
+ // go up the ancestors looking for an animation value
+ var climbScope = opts.enteringScope;
+ while(climbScope) {
+ if(climbScope.animation) {
+ return climbScope.animation;
+ }
+ climbScope = climbScope.$parent;
+ }
+ }
+ },
+
+ setAnimationClass: function(element, animationClass, navDirection) {
+ // add the animation we're gonna use
+ element[0].classList.add(animationClass);
+
+ if(navDirection === 'back') {
+ // animate backward
+ element[0].classList.add('reverse');
+ } else {
+ // defaults to animate forward
+ // make sure the reverse class isn't already added
+ element[0].classList.remove('reverse');
+ }
+ }
+
+ };
+
+}]);
diff --git a/js/ext/angular/test/directive/ionicTabBar.unit.js b/js/ext/angular/test/directive/ionicTabBar.unit.js
index 2b24cc485d..b5ff874b47 100644
--- a/js/ext/angular/test/directive/ionicTabBar.unit.js
+++ b/js/ext/angular/test/directive/ionicTabBar.unit.js
@@ -124,6 +124,23 @@ describe('Tabs directive', function() {
expect(angular.element(tabs).hasClass('tabs-positive')).toEqual(true);
expect(angular.element(tabs).hasClass('tabs-icon-bottom')).toEqual(true);
});
+
+ it('Has ui-view', function() {
+ element = compile('' +
+ '' +
+ 'content2' +
+ '')(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].hasUiView).toEqual(true);
+ expect(scope.controllers[0].uiViewName).toEqual('name1');
+ expect(scope.controllers[0].url).toEqual('/page1');
+ expect(scope.controllers[1].hasUiView).toEqual(false);
+ expect(scope.controllers[1].url).toEqual('/page2');
+ });
});
describe('Tab Item directive', function() {
diff --git a/js/ext/angular/test/service/ionicView.unit.js b/js/ext/angular/test/service/ionicView.unit.js
new file mode 100644
index 0000000000..3a7f18eb2c
--- /dev/null
+++ b/js/ext/angular/test/service/ionicView.unit.js
@@ -0,0 +1,754 @@
+describe('Ionic View Service', function() {
+ var viewService, rootScope, stateProvider, window;
+
+ beforeEach(module('ionic.service.view'));
+ beforeEach(module('ui.router'));
+
+ beforeEach(module(function ($stateProvider, $provide) {
+ stateProvider = $stateProvider;
+
+ $stateProvider
+ .state('home', { url: "/" })
+ .state('home.item', { url: "front/:id" })
+
+ .state('about', { url: "/about" })
+ .state('about.person', { url: "/person" })
+ .state('about.person.item', { url: "/id" })
+
+ .state('about.sidebar', {})
+ .state('about.sidebar.item', {})
+
+ .state('contact', { url: "/contact" })
+
+ .state('info', { url: "/info" })
+
+ .state('tabs', { abstract: true })
+ .state('tabs.tab1view1', {})
+ .state('tabs.tab1view2', {})
+ .state('tabs.tab1view3', {})
+
+ .state('tabs.tab2view1', {})
+ .state('tabs.tab2view2', {})
+ .state('tabs.tab2view3', {})
+
+ .state('tabs.tab3view1', {})
+ .state('tabs.tab3view2', {})
+ .state('tabs.tab3view3', {})
+
+ }));
+
+ beforeEach(inject(function(ViewService, $rootScope, $window, $location) {
+ viewService = ViewService;
+ rootScope = $rootScope;
+ window = $window;
+ window.history.go = function(val) { return val };
+ }));
+
+ it('should do nothing if the same state happens', inject(function($state) {
+ var uiViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ viewService.register(uiViewScope);
+
+ homeViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ registerData = viewService.register(homeViewScope);
+
+ expect(registerData.navAction).toEqual('noChange');
+ expect(registerData.historyId).toEqual('root');
+ }));
+
+ it('should create a new view', inject(function($location, $state) {
+ $location.url('/home');
+ var view1Scope = {};
+ var rsp = viewService.register(view1Scope);
+ expect(rsp.navAction).toEqual('initialView');
+ expect(rsp.historyId).toEqual('root');
+
+ var currentView = viewService.getCurrentView();
+ expect(currentView.viewId).toBeDefined();
+ expect(currentView.index).toEqual(0);
+ expect(currentView.historyId).toBeDefined();
+ expect(currentView.backViewId).toEqual(null);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(currentView.url).toEqual('/home');
+
+ var hist = rootScope.$viewHistory.histories.root;
+ expect(hist.cursor).toEqual(0);
+ expect(hist.stack.length).toEqual(1);
+ }));
+
+ it('should register two sequential views', inject(function($state) {
+ $state.go('home');
+ rootScope.$apply();
+ expect(viewService.getCurrentStateName()).toEqual('home');
+ var view1Scope = {};
+ var rsp = viewService.register(view1Scope);
+ expect(rootScope.$viewHistory.currentView.stateName).toEqual('home');
+
+ expect(rsp.viewId).not.toBeUndefined();
+ expect(rootScope.$viewHistory.histories[rsp.viewId].viewId).toEqual(rsp.viewId);
+ expect(viewService.getBackView()).toEqual(null);
+ expect(viewService.getForwardView()).toEqual(null);
+
+ expect(rootScope.$viewHistory.currentView.stateName).toEqual('home');
+ var currentView = viewService.getCurrentView();
+ expect(currentView.index).toEqual(0);
+
+ $state.go('about');
+ rootScope.$apply();
+ expect(viewService.getCurrentStateName()).toEqual('about');
+ rsp = viewService.register({});
+ expect(rsp.navAction).toEqual('newView');
+ expect(viewService.getCurrentView().stateName).toEqual('about');
+ expect(viewService.getBackView().stateName).toEqual('home');
+ expect(viewService.getForwardView()).toEqual(null);
+
+ var hist = rootScope.$viewHistory.histories.root;
+ expect(hist.cursor).toEqual(1);
+ expect(hist.stack.length).toEqual(2);
+ }));
+
+ it('should register views and go back to start', inject(function($state) {
+ $state.go('home');
+ rootScope.$apply();
+ var registerData = viewService.register({});
+ expect(viewService.getCurrentView().stateName).toEqual('home');
+ expect(viewService.getBackView()).toEqual(null);
+ expect(viewService.getForwardView()).toEqual(null);
+ expect(registerData.navDirection).toEqual(null);
+ expect(registerData.navAction).toEqual('initialView');
+
+ $state.go('about');
+ rootScope.$apply();
+ registerData = viewService.register({});
+ var currentView = viewService.getCurrentView();
+ var backView = viewService.getBackView();
+ var forwardView = viewService.getForwardView();
+ expect(currentView.stateName).toEqual('about');
+ expect(currentView.backViewId).toEqual(backView.viewId);
+ expect(backView.stateName).toEqual('home');
+ expect(forwardView).toEqual(null);
+ expect(registerData.navDirection).toEqual('forward');
+ expect(registerData.navAction).toEqual('newView');
+
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+
+ $state.go('contact');
+ rootScope.$apply();
+ registerData = viewService.register({});
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(backView.stateName).toEqual('about');
+ expect(currentView.backViewId).toEqual(backView.viewId);
+ expect(viewService.getForwardView()).toEqual(null);
+ expect(registerData.navDirection).toEqual('forward');
+ expect(registerData.navAction).toEqual('newView');
+
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(2);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(3);
+
+ $state.go('about');
+ rootScope.$apply();
+ registerData = viewService.register({});
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.stateName).toEqual('about');
+ expect(currentView.backViewId).toEqual(backView.viewId);
+ expect(currentView.forwardViewId).toEqual(forwardView.viewId);
+ expect(backView.stateName).toEqual('home');
+ expect(forwardView.stateName).toEqual('contact');
+ expect(registerData.navDirection).toEqual('back');
+ expect(registerData.navAction).toEqual('moveBack');
+
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(3);
+
+ $state.go('home');
+ rootScope.$apply();
+ registerData = viewService.register({});
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.stateName).toEqual('home');
+ expect(currentView.forwardViewId).toEqual(forwardView.viewId);
+ expect(backView).toEqual(null);
+ expect(forwardView.stateName).toEqual('about');
+ expect(registerData.navDirection).toEqual('back');
+ expect(registerData.navAction).toEqual('moveBack');
+
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(3);
+ }));
+
+ it('should register four views, and not go back to the first', inject(function($state) {
+ var homeViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg = viewService.register(homeViewScope);
+ expect(viewService.getCurrentStateName()).toEqual('home');
+ expect(viewService.getCurrentView().stateName).toEqual('home');
+ expect(viewService.getBackView()).toEqual(null);
+ expect(viewService.getForwardView()).toEqual(null);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(1);
+
+ var aboutViewScope = {};
+ $state.go('about');
+ rootScope.$apply();
+ var aboutReg = viewService.register(aboutViewScope);
+ var currentView = viewService.getCurrentView();
+ var backView = viewService.getBackView();
+ var forwardView = viewService.getForwardView();
+ expect(currentView.viewId).toEqual(aboutReg.viewId);
+ expect(currentView.backViewId).toEqual(homeReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(backView.viewId).toEqual(homeReg.viewId);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+
+ var tab1Scope = {};
+ viewService.registerHistory(tab1Scope);
+ var tab1view1Scope = { $parent: tab1Scope };
+
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ var tab1view1Reg = viewService.register(tab1view1Scope);
+
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].historyId).toEqual(tab1Scope.$historyId);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack[0].viewId).toEqual(tab1view1Reg.viewId);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.viewId).toEqual(tab1view1Reg.viewId);
+ expect(currentView.historyId).toEqual(tab1Scope.$historyId);
+ expect(currentView.historyId).toEqual(tab1view1Reg.historyId);
+ expect(currentView.backViewId).toEqual(aboutReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(backView.viewId).toEqual(aboutReg.viewId);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+
+ $state.go('home');
+ rootScope.$apply();
+ var home2reg = viewService.register({});
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.backViewId).toEqual(tab1view1Reg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(backView.viewId).toEqual(tab1view1Reg.viewId);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(2);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(3);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(1);
+ }));
+
+ it('should register views in the same history, go back, then overwrite the forward views', inject(function($state) {
+ var homeViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg = viewService.register(homeViewScope);
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.viewId).toEqual(homeReg.viewId);
+ expect(currentView.backViewId).toEqual(null);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(1);
+ expect(homeReg.navAction).toEqual('initialView');
+ expect(homeReg.navDirection).toEqual(null);
+
+ var aboutScope = {};
+ $state.go('about');
+ rootScope.$apply();
+ var aboutReg = viewService.register(aboutScope);
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.viewId).toEqual(aboutReg.viewId);
+ expect(currentView.backViewId).toEqual(homeReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(backView.viewId).toEqual(homeReg.viewId);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+ expect(aboutReg.navAction).toEqual('newView');
+ expect(aboutReg.navDirection).toEqual('forward');
+
+ homeViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg2 = viewService.register(homeViewScope);
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.viewId).toEqual(homeReg.viewId);
+ expect(currentView.backViewId).toEqual(null);
+ expect(currentView.forwardViewId).toEqual(aboutReg.viewId);
+ expect(forwardView.viewId).toEqual(aboutReg.viewId);
+ expect(forwardView.backViewId).toEqual(currentView.viewId);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+ expect(homeReg2.navAction).toEqual('moveBack');
+ expect(homeReg2.navDirection).toEqual('back');
+
+ // this should overwrite that we went to the "about" view
+ contactScope = {};
+ $state.go('contact');
+ rootScope.$apply();
+ var contactReg = viewService.register(contactScope);
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+ expect(currentView.backViewId).toEqual(homeReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+ expect(forwardView).toEqual(null);
+ expect(backView.viewId).toEqual(homeReg.viewId);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(2);
+ expect(contactReg.navAction).toEqual('newView');
+ expect(contactReg.navDirection).toEqual('forward');
+ }));
+
+ it('should go to a new history, come back out, go to same history and come back out', inject(function($state) {
+ var rootViewContainer = {};
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg = viewService.register(rootViewContainer);
+ var currentView = viewService.getCurrentView();
+ expect(currentView.historyId).toEqual('root');
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(homeReg.navAction).toEqual('initialView');
+
+ // each tab gets its own history in the tabs directive
+ // create a new tab and its history
+ var tabs1Container = { $parent: rootViewContainer };
+ viewService.registerHistory(tabs1Container);
+ expect(tabs1Container.$historyId).toBeDefined();
+ expect(rootViewContainer.$historyId).not.toEqual(tabs1Container.$historyId);
+ var originalTab1ViewId = tabs1Container.$historyId;
+
+ // the actual view within the tab renders
+ // nav to tab1 which has its own history
+ var tab1View = { $parent: tabs1Container };
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ var tab1view1Reg = viewService.register(tab1View);
+ currentView = viewService.getCurrentView();
+ expect(currentView.historyId).toEqual(tabs1Container.$historyId);
+ expect(rootScope.$viewHistory.histories[tabs1Container.$historyId].parentHistoryId).toEqual('root');
+ expect(rootScope.$viewHistory.histories[tabs1Container.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tabs1Container.$historyId].stack.length).toEqual(1);
+ expect(tab1view1Reg.navAction).toEqual('newView');
+
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+
+ expect(currentView.stateName).toEqual('tabs.tab1view1');
+ expect(currentView.viewId).toEqual(tab1view1Reg.viewId);
+ expect(currentView.backViewId).toEqual(homeReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+
+ expect(backView.stateName).toEqual('home');
+ expect(backView.backViewId).toEqual(null);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+
+ expect(forwardView).toEqual(null);
+
+ // nav back to the home in the root history
+ homeViewScope = {};
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg = viewService.register(homeViewScope);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(1);
+ expect(homeReg.historyId).toEqual('root');
+
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+
+ expect(currentView.stateName).toEqual('home');
+ expect(currentView.backViewId).toEqual(null);
+ expect(currentView.forwardViewId).toEqual(tab1view1Reg.viewId);
+
+ expect(forwardView.stateName).toEqual('tabs.tab1view1');
+ expect(forwardView.viewId).toEqual(tab1view1Reg.viewId);
+ expect(forwardView.backViewId).toEqual(currentView.viewId);
+ expect(forwardView.forwardViewId).toEqual(null);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories.root.stack.length).toEqual(1);
+
+ // create a new tab and its history
+ tabs1Container = { $parent: rootViewContainer };
+ viewService.registerHistory(tabs1Container);
+ expect(originalTab1ViewId).not.toEqual(tabs1Container.$historyId);
+
+ tab1View = { $parent: tabs1Container };
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ tab1view1Reg = viewService.register(tab1View);
+ expect(tab1view1Reg.navAction).toEqual('moveForward');
+ expect(tab1view1Reg.navDirection).toEqual(null);
+ expect(tab1view1Reg.historyId).toEqual(originalTab1ViewId);
+ expect(originalTab1ViewId).toEqual(tabs1Container.$historyId);
+ expect(tab1view1Reg.historyId).not.toEqual('root');
+ expect(rootScope.$viewHistory.histories[tab1view1Reg.historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1view1Reg.historyId].stack.length).toEqual(1);
+
+ currentView = viewService.getCurrentView();
+ expect(currentView.historyId).toEqual(tabs1Container.$historyId);
+ expect(rootScope.$viewHistory.histories[tabs1Container.$historyId].cursor).toEqual(0);
+
+ currentView = viewService.getCurrentView();
+ backView = viewService.getBackView();
+ forwardView = viewService.getForwardView();
+
+ expect(currentView.stateName).toEqual('tabs.tab1view1');
+ expect(currentView.viewId).toEqual(tab1view1Reg.viewId);
+ expect(currentView.backViewId).toEqual(homeReg.viewId);
+ expect(currentView.forwardViewId).toEqual(null);
+
+ expect(backView.viewId).toEqual(homeReg.viewId);
+ expect(backView.stateName).toEqual('home');
+ expect(backView.backViewId).toEqual(null);
+ expect(backView.forwardViewId).toEqual(currentView.viewId);
+
+ expect(forwardView).toEqual(null);
+ expect(rootScope.$viewHistory.histories.root.cursor).toEqual(0);
+ }));
+
+ it('should nav to a history, move around in it, and come back', inject(function($state) {
+ // go to the first page
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg = viewService.register({});
+
+ // each tab gets its own history in the tabs directive
+ var tab1Scope = { };
+ var tab2Scope = { };
+ var tab3Scope = { };
+ viewService.registerHistory(tab1Scope);
+ viewService.registerHistory(tab2Scope);
+ viewService.registerHistory(tab3Scope);
+
+ // the actual view renders
+ var tab1view1Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ var tab1view1ScopeReg = viewService.register(tab1view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view1');
+ expect(viewService.getBackView().stateName).toEqual('home');
+ expect(viewService.getForwardView()).toEqual(null);
+ var lastView = viewService.getCurrentView();
+ expect(lastView.index).toEqual(0);
+ expect(tab1view1ScopeReg.viewId).toEqual(lastView.viewId);
+ expect(tab1view1ScopeReg.navAction).toEqual('newView');
+ expect(tab1view1ScopeReg.navDirection).toEqual(null);
+ expect(rootScope.$viewHistory.histories[tab1view1ScopeReg.historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1view1ScopeReg.historyId].stack.length).toEqual(1);
+
+ // inside first tab, go to another list inside the same tab
+ var tab1view2Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view2');
+ rootScope.$apply();
+ var tab1view2ScopeReg = viewService.register(tab1view2Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view2');
+ expect(viewService.getBackView().stateName).toEqual('tabs.tab1view1');
+ expect(viewService.getForwardView()).toEqual(null);
+ var lastView = viewService.getCurrentView();
+ expect(lastView.index).toEqual(1);
+ expect(tab1view2ScopeReg.viewId).toEqual(lastView.viewId);
+ expect(tab1view2ScopeReg.navAction).toEqual('newView');
+ expect(tab1view2ScopeReg.navDirection).toEqual('forward');
+ expect(rootScope.$viewHistory.histories[tab1view2ScopeReg.historyId].cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories[tab1view2ScopeReg.historyId].stack.length).toEqual(2);
+
+ // go back one within the tab
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ var tab1view1Scope2Reg = viewService.register(tab1view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view1');
+ expect(viewService.getBackView().stateName).toEqual('home');
+ expect(viewService.getForwardView().stateName).toEqual('tabs.tab1view2');
+ var lastView = viewService.getCurrentView();
+ expect(lastView.index).toEqual(0);
+ expect(tab1view1Scope2Reg.navAction).toEqual('moveBack');
+ expect(tab1view1Scope2Reg.navDirection).toEqual('back');
+ expect(rootScope.$viewHistory.histories[tab1view1Scope2Reg.historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1view1Scope2Reg.historyId].stack.length).toEqual(2);
+
+ // go back again, and should break out of the tab's history
+ $state.go('home');
+ rootScope.$apply();
+ var homeReg2 = viewService.register({});
+ expect(viewService.getCurrentStateName()).toEqual('home');
+ expect(homeReg2.historyId).toEqual('root');
+ expect(homeReg2.navAction).toEqual('moveBack');
+ expect(homeReg2.navDirection).toEqual(null);
+ expect(rootScope.$viewHistory.histories[homeReg2.historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[homeReg2.historyId].stack.length).toEqual(1);
+
+ $state.go('about');
+ rootScope.$apply();
+ var aboutReg = viewService.register({});
+ expect(viewService.getCurrentStateName()).toEqual('about');
+ expect(aboutReg.historyId).toEqual('root');
+ expect(aboutReg.navAction).toEqual('newView');
+ expect(aboutReg.navDirection).toEqual('forward');
+ expect(rootScope.$viewHistory.histories[aboutReg.historyId].cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories[aboutReg.historyId].stack.length).toEqual(2);
+ }));
+
+ it('should change to history that already exists, and go to its last current view', inject(function($location, $state) {
+ // register tabs
+ var tab1Scope = {};
+ var tab2Scope = {};
+ viewService.registerHistory(tab1Scope);
+ viewService.registerHistory(tab2Scope);
+ var orgTab1HistoryId = tab1Scope.$historyId;
+
+ // render first view in tab1
+ var tab1view1Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ var registerData = viewService.register(tab1view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view1');
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(1);
+ expect(registerData.navAction).toEqual('initialView');
+ expect(registerData.navDirection).toEqual(null);
+
+ // render second view in tab1
+ var tab1view2Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view2');
+ rootScope.$apply();
+ registerData = viewService.register(tab1view2Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view2');
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(1);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(2);
+ expect(registerData.navAction).toEqual('newView');
+ expect(registerData.navDirection).toEqual('forward');
+ currentView = viewService.getCurrentView();
+ expect(currentView.viewId).toEqual(registerData.viewId);
+
+ // go back to the first view again in tab 1
+ tab1view1Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view1');
+ rootScope.$apply();
+ registerData = viewService.register(tab1view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view1');
+ currentView = viewService.getCurrentView();
+ expect(currentView.viewId).toEqual(registerData.viewId);
+ forwardView = viewService.getForwardView();
+ expect(currentView.forwardViewId).toEqual(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack[1].viewId);
+ expect(forwardView.backViewId).toEqual(currentView.viewId);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(2);
+ expect(registerData.navAction).toEqual('moveBack');
+ expect(registerData.navDirection).toEqual('back');
+
+ // render first view in tab2
+ var tab2view1Scope = { $parent: tab2Scope };
+ $state.go('tabs.tab2view1');
+ rootScope.$apply();
+ registerData = viewService.register(tab2view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab2view1');
+ expect(rootScope.$viewHistory.histories[tab2Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab2Scope.$historyId].stack.length).toEqual(1);
+ expect(registerData.navAction).toEqual('newView');
+ expect(registerData.navDirection).toEqual(null);
+ var tab2view1ViewId = registerData.viewId;
+
+ // tab1's forward history should be destroyed
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(1);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack[0].forwardViewId).toEqual(registerData.viewId);
+
+ // go back to tab1, and it should load the first view of tab1
+ expect(tab1Scope.$historyId).toEqual(orgTab1HistoryId);
+ rootScope.$broadcast("viewState.changeHistory", { historyId: tab1Scope.$historyId, enableUrlChange: false });
+ rootScope.$apply();
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view1');
+ registerData = viewService.register(tab1view1Scope);
+ expect(registerData.navAction).toEqual('moveBack');
+ expect(registerData.navDirection).toEqual(null);
+
+ currentView = viewService.getCurrentView();
+ expect(currentView.viewId).toEqual(registerData.viewId);
+ expect(currentView.historyId).toEqual(orgTab1HistoryId);
+ expect(currentView.forwardViewId).toEqual(tab2view1ViewId);
+
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(0);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(1);
+
+ currentView = viewService.getCurrentView();
+ expect(currentView.stateName).toEqual('tabs.tab1view1');
+ expect(currentView.historyId).toEqual(tab1Scope.$historyId);
+
+ // go to view 2 in tab 1
+ tab1view2Scope = { $parent: tab1Scope };
+ $state.go('tabs.tab1view2');
+ rootScope.$apply();
+ registerData = viewService.register(tab1view2Scope);
+ expect(registerData.historyId).toEqual(orgTab1HistoryId);
+ var tab1view2ViewId = registerData.viewId;
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view2');
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].stack.length).toEqual(2);
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(1);
+ expect(registerData.navAction).toEqual('newView');
+ expect(registerData.navDirection).toEqual('forward');
+
+ // go to view 1 in tab 2
+ tab2view1Scope = { $parent: tab2Scope };
+ $state.go('tabs.tab2view1');
+ rootScope.$apply();
+ registerData = viewService.register(tab2view1Scope);
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab2view1');
+ expect(rootScope.$viewHistory.histories[tab2Scope.$historyId].cursor).toEqual(0);
+ expect(registerData.navAction).toEqual('newView');
+ expect(registerData.navDirection).toEqual(null);
+ currentView = viewService.getCurrentView();
+ expect(currentView.backViewId).toEqual(tab1view2ViewId);
+ expect(currentView.forwardViewId).toEqual(null);
+
+ // should be remembered at the tab 1 view 2
+ rootScope.$broadcast("viewState.changeHistory", { historyId: tab1Scope.$historyId });
+ rootScope.$apply();
+ expect(viewService.getCurrentStateName()).toEqual('tabs.tab1view2');
+ expect(rootScope.$viewHistory.histories[tab1Scope.$historyId].cursor).toEqual(1);
+ }));
+
+ it('should init root viewHistory data', inject(function() {
+ expect(rootScope.$viewHistory.backView).toEqual(null);
+ expect(rootScope.$viewHistory.currentView).toEqual(null);
+ expect(rootScope.$viewHistory.forwardView).toEqual(null);
+ expect(rootScope.$viewHistory.histories).toEqual({
+ root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 }
+ });
+ }));
+
+ it('should create a viewService view', inject(function($location) {
+ var newView = viewService.createView();
+ expect(newView).toEqual(null);
+
+ newView = viewService.createView({ stateName: 'about', url: '/url', });
+ expect(newView.stateName).toEqual('about');
+ }));
+
+ it('should go() to a view', inject(function($location) {
+ var newView = viewService.createView({ stateName: 'about' });
+ newView.go();
+ rootScope.$apply();
+ expect($location.url()).toEqual('/about');
+
+ $location.url('/nochange');
+ newView = viewService.createView({ url: '/nochange' });
+ var result = newView.go();
+ expect(result).toEqual(null);
+
+ $location.url('/nochange')
+ newView = viewService.createView({ url: '/nochange' });
+ result = newView.go();
+ expect(result).toEqual(null);
+
+ newView = rootScope.$viewHistory.backView = viewService.createView({ url: '/url' });
+ result = newView.go();
+ expect(result).toEqual(-1);
+
+ newView = rootScope.$viewHistory.forwardView = viewService.createView({ url: '/url' });
+ result = newView.go();
+ expect(result).toEqual(1);
+
+ newView = viewService.createView({ url: '/url' });
+ newView.go();
+ expect($location.url()).toEqual('/url');
+ }));
+
+ it('should change history on event changeHistory', inject(function($location, $state) {
+ $location.url('/original');
+
+ rootScope.$broadcast("viewState.changeHistory");
+ expect($location.url()).toEqual('/original');
+
+ rootScope.$broadcast("viewState.changeHistory", { uiSref: 'about' });
+ expect($location.url()).toEqual('/about');
+
+ rootScope.$broadcast("viewState.changeHistory", { url: '/url' });
+ expect($location.url()).toEqual('/url');
+
+ rootScope.$viewHistory.histories['h123'] = { stack: [], cursor: -1 }
+ rootScope.$broadcast("viewState.changeHistory", { historyId: 'h123' });
+ expect($location.url()).toEqual('/url');
+
+ var newView = viewService.createView({ stateName: 'about' });
+ rootScope.$viewHistory.histories['h123'].stack.push(newView);
+ rootScope.$viewHistory.histories['h123'].cursor++;
+ rootScope.$broadcast("viewState.changeHistory", { historyId: 'h123' });
+ rootScope.$apply();
+ expect($state.current.name).toEqual('about');
+ }));
+
+ it('should update document title', inject(function($document) {
+ $document[0].title = 'Original Title';
+
+ rootScope.$broadcast("viewState.viewEnter");
+ expect($document[0].title).toEqual('Original Title');
+
+ rootScope.$broadcast("viewState.viewEnter", {});
+ expect($document[0].title).toEqual('Original Title');
+
+ rootScope.$broadcast("viewState.viewEnter", { title: 'New Title' });
+ expect($document[0].title).toEqual('New Title');
+ }));
+
+ it('should transition w/out animation', inject(function($compile) {
+ var opts = {};
+ opts.parentElement = $compile("