From 64b98beb4b06e715abd255e1ba9018aa6ea28be0 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Sat, 15 Mar 2014 10:39:18 -0600 Subject: [PATCH] chore(): prepare for 0.9.27 - last alpha release --- gulpfile.js | 12 +- js/ext/angular/src/directive/ionicBar.js | 174 ++++---- js/ext/angular/src/directive/ionicNavBar.js | 384 ------------------ .../angular/src/directive/ionicViewState.js | 276 ++++++++++++- js/ext/angular/src/ionicAngular.js | 33 +- .../delegates/ionicSideMenuDelegate.js | 40 ++ .../angular/test/directive/ionicBar.unit.js | 117 +++--- .../test/directive/ionicNavBackButton.unit.js | 71 ---- .../test/directive/ionicNavBar.unit.js | 254 ------------ .../angular/test/directive/ionicNavButtons.js | 67 --- .../angular/test/directive/ionicView.unit.js | 294 +++++++++++--- test/e2e/viewState/test.html | 31 +- 12 files changed, 745 insertions(+), 1008 deletions(-) create mode 100644 js/ext/angular/src/service/delegates/ionicSideMenuDelegate.js delete mode 100644 js/ext/angular/test/directive/ionicNavBar.unit.js delete mode 100644 js/ext/angular/test/directive/ionicNavButtons.js diff --git a/gulpfile.js b/gulpfile.js index 6d40f8a851..8b9c6288ee 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,5 +1,6 @@ var _ = require('lodash'); var buildConfig = require('./config/build.config.js'); +var changelog = require('conventional-changelog'); var connect = require('connect'); var dgeni = require('dgeni'); var http = require('http'); @@ -56,8 +57,15 @@ gulp.task('watch', function() { gulp.watch('scss/**/*.scss', ['sass']); }); -gulp.task('changelog', function() { - gutil.log(gutil.colors.red('TODO: Add working changelog task')); +gulp.task('changelog', function(done) { + changelog({ + repository: pkg.repository.url, + version: pkg.version, + }, function(err, data) { + if (err) return done(err); + require('fs').writeFileSync('CHANGELOG.md', data); + done(); + }); }); gulp.task('bundle', [ diff --git a/js/ext/angular/src/directive/ionicBar.js b/js/ext/angular/src/directive/ionicBar.js index 032ae2b7af..c07c67b3be 100644 --- a/js/ext/angular/src/directive/ionicBar.js +++ b/js/ext/angular/src/directive/ionicBar.js @@ -18,100 +18,108 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) * @name ionHeaderBar * @module ionic * @restrict E - * @group page layout - * @controller ionicBar - * * @description - * Adds a fixed header bar above some content. + * While Ionic provides simple Header and Footer bars that can be created through + * HTML and CSS alone, Header bars specifically can be extended in order to + * provide dynamic layout features such as auto-title centering and animation. + * They are also used by the Views and Navigation Controller to animate a title + * on navigation and toggle a back button. * - * Is able to have left or right buttons, and additionally its title can be - * aligned through the {@link ionic.controller:ionicBar ionicBar controller}. + * The main header bar feature provided is auto title centering. + * In this situation, the title text will center itself until either the + * left or right button content is too wide for the label to center. + * In that case, it will slide left or right until it can fit. + * You can also align the title left for a more Android-friendly header. * - * @param {string=} model The model to assign this headerBar's - * {@link ionic.controller:ionicBar ionicBar controller} to. - * Defaults to assigning to $scope.headerBarController. - * @param {string=} align-title Where to align the title at the start. - * Avaialble: 'left', 'right', or 'center'. Defaults to 'center'. + * Using two-way data binding, the header bar will automatically + * readjust the heading title alignment when the title or buttons change. + * + * @param {string} title The title use on the headerBar. + * @param {expression=} leftButtons Point to an array of buttons to put on the left of the bar. + * @param {expression=} rightButtons Point to an array of buttons to put on the right of the bar. + * @param {string=} type The type of the bar, for example 'bar-positive'. + * @param {string=} align Where to align the title. 'left', 'right', or 'center'. Defaults to 'center'. * * @usage * ```html - * - *
- * - *
- *

Title!

- *
- * - *
+ * * - * - * Some content! - * * ``` + * */ -.directive('ionHeaderBar', barDirective(true)) +.directive('ionHeaderBar', ['$ionicScrollDelegate', function($ionicScrollDelegate) { + return { + restrict: 'E', + replace: true, + transclude: true, + template: '
\ +
\ + \ +
\ +

\ +
\ + \ +
\ +
', -/** - * @ngdoc directive - * @name ionFooterBar - * @module ionic - * @restrict E - * @group page layout - * @controller ionicBar - * - * @description - * Adds a fixed footer bar below some content. - * - * Is able to have left or right buttons, and additionally its title can be - * aligned through the {@link ionic.controller:ionicBar ionicBar controller}. - * - * @param {string=} model The model to assign this footerBar's - * {@link ionic.controller:ionicBar ionicBar controller} to. - * Defaults to assigning to $scope.footerBarController. - * @param {string=} align-title Where to align the title at the start. - * Avaialble: 'left', 'right', or 'center'. Defaults to 'center'. - * - * @usage - * ```html - * - * Some content! - * - * - *
- * - *
- *

Title!

- *
- * - *
- *
- * ``` - */ -.directive('ionFooterBar', barDirective(false)); + scope: { + leftButtons: '=', + rightButtons: '=', + title: '@', + type: '@', + alignTitle: '@' + }, + link: function($scope, $element, $attr) { + var hb = new ionic.views.HeaderBar({ + el: $element[0], + alignTitle: $scope.alignTitle || 'center' + }); -function barDirective(isHeader) { - var BAR_TEMPLATE = isHeader ? - '
' : - '
'; - var BAR_MODEL_DEFAULT = isHeader ? - 'headerBarController' : - 'footerBarController'; - return ['$parse', function($parse) { - return { - restrict: 'E', - replace: true, - transclude: true, - template: BAR_TEMPLATE, - link: function($scope, $element, $attr) { - var hb = new ionic.views.HeaderBar({ - el: $element[0], - alignTitle: $attr.alignTitle || 'center' - }); + $element.addClass($scope.type); - $parse($attr.model || BAR_MODEL_DEFAULT).assign($scope.$parent, hb); - } - }; - }]; -} + $scope.headerBarView = hb; + + $scope.$watchCollection('leftButtons', function(val) { + // Resize the title since the buttons have changed + hb.align(); + }); + + $scope.$watchCollection('rightButtons', function(val) { + // Resize the title since the buttons have changed + hb.align(); + }); + + $scope.$watch('title', function(val) { + // Resize the title since the title has changed + hb.align(); + }); + } + }; +}]) + +.directive('ionFooterBar', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + template: '
\ +
', + + scope: { + type: '@', + }, + + link: function($scope, $element, $attr) { + $element.addClass($scope.type); + } + }; +}); })(ionic); diff --git a/js/ext/angular/src/directive/ionicNavBar.js b/js/ext/angular/src/directive/ionicNavBar.js index 33c6cc0e69..e69de29bb2 100644 --- a/js/ext/angular/src/directive/ionicNavBar.js +++ b/js/ext/angular/src/directive/ionicNavBar.js @@ -1,384 +0,0 @@ - -angular.module('ionic.ui.navBar', ['ionic.service.view', 'ngSanitize']) - -/** - * @ngdoc controller - * @name ionicNavBar - * @module ionic - * @group navigation - * @description - * Controller for the {@link ionic.directive:ionNavBar} directive. - */ -.controller('$ionicNavBar', ['$scope', '$element', '$ionicViewService', '$animate', '$compile', -function($scope, $element, $ionicViewService, $animate, $compile) { - //Let the parent know about our controller too so that children of - //sibling content elements can know about us. - $element.parent().data('$ionNavBarController', this); - - var hb = this._headerBarView = new ionic.views.HeaderBar({ - el: $element[0], - alignTitle: $scope.alignTitle || 'center' - }); - - this.leftButtonsElement = angular.element( - $element[0].querySelector('.buttons.left-buttons') - ); - this.rightButtonsElement = angular.element( - $element[0].querySelector('.buttons.right-buttons') - ); - - /** - * @ngdoc method - * @name ionicNavBar#back - * @description Goes back in the view history. - * @param {DOMEvent=} event The event object (eg from a tap event) - */ - this.back = function(e) { - var backView = $ionicViewService.getBackView(); - backView && backView.go(); - e && (e.alreadyHandled = true); - return false; - }; - - /** - * @ngdoc method - * @name ionicNavBar#align - * @description Calls {@link ionic.controller:ionicBar#align ionicBar#align} for this navBar. - * @param {string=} direction The direction to the align the title text towards. - */ - this.align = function(direction) { - this._headerBarView.align(direction); - }; - - /** - * @ngdoc method - * @name ionicNavBar#showBackButton - * @description - * Set whether the {@link ionic.directive:ionNavBackButton} should be shown (if it exists). - * @param {boolean} show Whether to show the back button. - */ - this.showBackButton = function(show) { - $scope.backButtonShown = !!show; - }; - /** - * @ngdoc method - * @name ionicNavBar#showBar - * @description - * Set whether the {@link ionic.directive:ionNavBar} should be shown. - * @param {boolean} show Whether to show the bar. - */ - this.showBar = function(show) { - $scope.isInvisible = !show; - }; - /** - * @ngdoc method - * @name ionicNavBar#setTitle - * @description - * Set the title for the {@link ionic.directive:ionNavBar}. - * @param {string} title The new title to show. - */ - this.setTitle = function(title) { - $scope.oldTitle = $scope.title; - $scope.title = title || ''; - }; - /** - * @ngdoc method - * @name ionicNavBar#changeTitle - * @description - * Change the title, transitioning the new title in and the old one out in a given direction. - * @param {string} title The new title to show. - * @param {string} direction The direction to transition the new title in. - * Available: 'forward', 'back'. - */ - this.changeTitle = function(title, direction) { - if ($scope.title === title) { - return false; - } - this.setTitle(title); - $scope.isReverse = direction == 'back'; - $scope.shouldAnimate = !!direction; - - if (!$scope.shouldAnimate) { - //We're done! - this._headerBarView.align(); - } else { - this._animateTitles(); - } - return true; - }; - - /** - * @private - * Exposed for testing - */ - this._animateTitles = function() { - var oldTitleEl, newTitleEl, currentTitles; - - //If we have any title right now - //(or more than one, they could be transitioning on switch), - //replace the first one with an oldTitle element - currentTitles = $element[0].querySelectorAll('.title'); - if (currentTitles.length) { - oldTitleEl = $compile('

')($scope); - angular.element(currentTitles[0]).replaceWith(oldTitleEl); - } - //Compile new title - newTitleEl = $compile('

')($scope); - - //Animate in on next frame - ionic.requestAnimationFrame(function() { - - oldTitleEl && $animate.leave(angular.element(oldTitleEl)); - - var insert = oldTitleEl && angular.element(oldTitleEl) || null; - $animate.enter(newTitleEl, $element, insert, function() { - hb.align(); - }); - - //Cleanup any old titles leftover (besides the one we already did replaceWith on) - angular.forEach(currentTitles, function(el) { - if (el && el.parentNode) { - //Use .remove() to cleanup things like .data() - angular.element(el).remove(); - } - }); - - //$apply so bindings fire - $scope.$digest(); - - //Stop flicker of new title on ios7 - ionic.requestAnimationFrame(function() { - newTitleEl[0].classList.remove('invisible'); - }); - }); - }; -}]) - -/** - * @ngdoc directive - * @name ionNavBar - * @module ionic - * @group navigation - * @controller ionicNavBar - * @restrict E - * - * @description - * If we have an {@link ionic.directive:ionNavView} directive, we can also create an - * ``, which will create a topbar that updates as the application state changes. - * - * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside. - * - * We can add buttons depending on the currently visible view using - * {@link ionic.directive:ionNavButtons}. - * - * @usage - * - * ```html - * - * - * - * - * - * - * - * ``` - * - * @param model {string=} The model to assign the - * {@link ionic.controller:ionicNavBar ionicNavBar controller} to. - * Default: assigns it to $scope.navBarController. - * @param animation {string=} The animation used to transition between titles. - * @param type {string=} The className for the navbar. For example, 'bar-positive'. - * @param align {string=} Where to align the title of the navbar. - * Available: 'left', 'right', 'center'. Defaults to 'center'. - */ -.directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile', '$parse', -function($ionicViewService, $rootScope, $animate, $compile, $parse) { - - return { - restrict: 'E', - replace: true, - transclude: true, - controller: '$ionicNavBar', - scope: { - animation: '@', - type: '@', - alignTitle: '@' - }, - template: - '', - compile: function(tElement, tAttrs, transclude) { - - return function link($scope, $element, $attr, navBarCtrl) { - $parse($attr.model || 'navBarController') - .assign($scope.$parent, navBarCtrl); - - //Put transcluded content (usually a back button) before the rest - transclude($scope, function(clone) { - $element.prepend(clone); - }); - - //defaults - $scope.backButtonShown = false; - $scope.shouldAnimate = true; - $scope.isReverse = false; - $scope.isInvisible = true; - - $scope.navBarClass = function() { - return ($scope.type ? ' ' + $scope.type : '') + - ($scope.isReverse ? ' reverse' : '') + - ($scope.isInvisible ? ' invisible' : '') + - ($scope.shouldAnimate && $scope.animation ? ' ' + $scope.animation : ''); - }; - }; - } - }; -}]) - -/** - * @ngdoc directive - * @name ionNavBackButton - * @module ionic - * @restrict E - * @group navigation - * @parent ionNavBar - * @description - * Creates a back button inside an {@link ionic.directive:ionNavBar}. - * - * Will show up when the user is able to go back in the current navigation stack. - * - * By default, will go back when clicked. If you wish to set a custom action on click, - * simply define an `ng-click` attribute and use - * {@link ionic.controller:ionicNavBar#back ionicNavBar controller's .back method} to go back - * when wished. - * - * @usage - * - * With default click action: - * - * ```html - * - * - * Back! - * - * - * ``` - * - * With custom click action: - * - * ```html - * - * - * Back! - * - * - * ``` - */ -.directive('ionNavBackButton', ['$ionicNgClick', function($ionicNgClick) { - return { - restrict: 'E', - require: '^ionNavBar', - replace: true, - transclude: true, - template: - '', - link: function($scope, $element, $attr, navBarCtrl) { - $scope.$navBack = navBarCtrl.back; - if (!$attr.ngClick) { - $ionicNgClick($scope, $element, '$navBack($event)'); - } - - //If the current viewstate does not allow a back button, - //always hide it. - var deregisterListener = $scope.$parent.$on( - '$viewHistory.historyChange', - function(e, data) { - $scope.hasBackButton = !!data.showBack; - } - ); - $scope.$on('$destroy', deregisterListener); - - //Make sure both that a backButton is allowed in the first place, - //and that it is shown by the current view. - $scope.$watch('!!(backButtonShown && hasBackButton)', function(val) { - $element.toggleClass('hide', !val); - }); - } - }; -}]) - -/** - * @ngdoc directive - * @name ionNavButtons - * @module ionic - * @restrict E - * @group navigation - * @parent ionNavView - * - * @description - * Use ionNavButtons to set the buttons on your {@link ionic.directive:ionNavBar} - * from within an {@link ionic.directive:ionView}. - * - * Any buttons you declare will be placed onto the navbar's corresponding side, - * and then destroyed when the user leaves their parent view. - * - * @usage - * ```html - * - * - * - * - * - * - * - * - * Some super content here! - * - * - * - * ``` - * - * @param {string} side The side to place the buttons on in the parent - * {@link ionic.directive:ionNavBar}. Available: 'left' or 'right'. - */ -.directive('ionNavButtons', ['$compile', '$animate', function($compile, $animate) { - return { - require: '^ionNavBar', - transclude: true, - restrict: 'E', - compile: function($element, $attrs, transclude) { - return function($scope, $element, $attrs, navBarCtrl) { - var navElement = $attrs.side === 'right' ? - navBarCtrl.rightButtonsElement : - navBarCtrl.leftButtonsElement; - - //Put all of our inside buttons into their own div, - //so we can remove them all when this element dies - - //even if the buttons have changed through an ng-repeat or the like, - //we just remove their div parent and they are gone. - var clone = angular.element('
').append(transclude($scope)); - $animate.enter(clone, navElement); - - //When our ion-nav-buttons container is destroyed, - //destroy everything in the navbar - $element.on('$destroy', function() { - $animate.leave(clone); - }); - - //The original element is just a completely empty - make it invisible - $element.css('display', 'none'); - }; - } - }; -}]); diff --git a/js/ext/angular/src/directive/ionicViewState.js b/js/ext/angular/src/directive/ionicViewState.js index cd400185dc..5f70acb814 100644 --- a/js/ext/angular/src/directive/ionicViewState.js +++ b/js/ext/angular/src/directive/ionicViewState.js @@ -3,13 +3,203 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture', 'ngSanitize']) +/** + * @ngdoc directive + * @name ionNavBar + * @module ionic + * @restrict E + * + * @usage + * If have an {@link ionic.directive:ionNavView} directive, we can also create an + * , which will create a topbar that updates as the application state changes. + * We can also add some styles and set up animations: + * + * ```html + * + * + * + * + * + * + * + * ``` + * + * @param {string=} back-button-type The type of the back button's icon. Available: 'button-icon' or just 'button'. + * @param {string=} back-button-icon The icon to use for the back button. For example, 'ion-arrow-left-c'. + * @param {string=} back-button-label The label to use for the back button. For example, 'Back'. + * @param animation {string=} The animation used to transition between titles. + * @param type {string=} The className for the navbar. For example, 'bar-positive'. + * @param align {string=} Where to align the title of the navbar. Available: 'left', 'right', 'center'. Defaults to 'center'. + */ +.directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile', + function( $ionicViewService, $rootScope, $animate, $compile) { + + return { + restrict: 'E', + replace: true, + scope: { + animation: '@', + type: '@', + backType: '@backButtonType', + backLabel: '@backButtonLabel', + backIcon: '@backButtonIcon', + alignTitle: '@' + }, + controller: function() {}, + template: + '', + compile: function(tElement, tAttrs) { + + return function link($scope, $element, $attr) { + //defaults + $scope.backButtonEnabled = false; + $scope.animateEnabled = true; + $scope.isReverse = false; + $scope.isInvisible = true; + + $scope.navBarClass = function() { + return ($scope.type ? ' ' + $scope.type : '') + + ($scope.isReverse ? ' reverse' : '') + + ($scope.isInvisible ? ' invisible' : '') + + (!$scope.animationDisabled && $scope.animation ? ' ' + $scope.animation : ''); + }; + + // 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; + + //Navbar events + $scope.$on('viewState.viewEnter', function(e, data) { + updateHeaderData(data); + }); + $scope.$on('viewState.showNavBar', function(e, showNavBar) { + $scope.isInvisible = !showNavBar; + }); + + // All of these these are emitted from children of a sibling scope, + // so we listen on parent so we can catch them as they bubble up + var unregisterEventListeners = [ + $scope.$parent.$on('$viewHistory.historyChange', function(e, data) { + $scope.backButtonEnabled = !!data.showBack; + }), + $scope.$parent.$on('viewState.leftButtonsChanged', function(e, data) { + $scope.leftButtons = data; + }), + $scope.$parent.$on('viewState.rightButtonsChanged', function(e, data) { + $scope.rightButtons = data; + }), + $scope.$parent.$on('viewState.showBackButton', function(e, data) { + $scope.backButtonEnabled = !!data; + }), + $scope.$parent.$on('viewState.titleUpdated', function(e, data) { + $scope.title = data && data.title || ''; + }) + ]; + $scope.$on('$destroy', function() { + for (var i=0; i')($scope); + angular.element(currentTitles[0]).replaceWith(oldTitleEl); + } + //Compile new title + newTitleEl = $compile('

')($scope); + + //Animate in one frame + ionic.requestAnimationFrame(function() { + + oldTitleEl && $animate.leave(angular.element(oldTitleEl)); + + var insert = oldTitleEl && angular.element(oldTitleEl) || null; + $animate.enter(newTitleEl, $element, insert, function() { + hb.align(); + }); + + //Cleanup any old titles leftover (besides the one we already did replaceWith on) + angular.forEach(currentTitles, function(el) { + if (el && el.parentNode) { + //Use .remove() to cleanup things like .data() + angular.element(el).remove(); + } + }); + + //$apply so bindings fire + $scope.$digest(); + + //Stop flicker of new title on ios7 + ionic.requestAnimationFrame(function() { + newTitleEl[0].classList.remove('invisible'); + }); + }); + } + }; + } + }; +}]) + /** * @ngdoc directive * @name ionView * @module ionic * @restrict E - * @group navigation - * @parent ionic.directive:ionNavBar + * @parent ionNavBar * * @description * A container for content, used to tell a parent {@link ionic.directive:ionNavBar} @@ -29,9 +219,10 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu * * ``` * + * @param {expression=} left-buttons The leftButtons to display on the parent {@link ionic.directive:ionNavBar}. + * @param {expression=} right-buttons The rightButtons to display on the parent {@link ionic.directive:ionNavBar}. * @param {string=} title The title to display on the parent {@link ionic.directive:ionNavBar}. - * @param {boolean=} hideBackButton Whether to hide the back button on the parent - * {@link ionic.directive:ionNavBar}. + * @param {boolean=} hideBackButton Whether to hide the back button on the parent {@link ionic.directive:ionNavBar}. * @param {boolean=} hideNavBar Whether to hide the parent {@link ionic.directive:ionNavBar}. */ .directive('ionView', ['$ionicViewService', '$rootScope', '$animate', @@ -39,34 +230,46 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu return { restrict: 'EA', priority: 1000, - require: '^?ionNavBar', scope: { + leftButtons: '=', + rightButtons: '=', title: '@', - hideBackButton: '&', - hideNavBar: '&', + hideBackButton: '@', + hideNavBar: '@', }, + compile: function(tElement, tAttrs, transclude) { tElement.addClass('pane'); tElement[0].removeAttribute('title'); - return function link($scope, $element, $attr, navBarCtrl) { - if (!navBarCtrl) { - return; - } - navBarCtrl.changeTitle($scope.title, $scope.$parent.$navDirection); + return function link($scope, $element, $attr) { + + $rootScope.$broadcast('viewState.viewEnter', { + title: $scope.title, + navDirection: $scope.$navDirection || $scope.$parent.$navDirection + }); // Should we hide a back button when this tab is shown - navBarCtrl.showBackButton(!$scope.hideBackButton()); + $scope.hideBackButton = $scope.$eval($scope.hideBackButton); + if($scope.hideBackButton) { + $rootScope.$broadcast('viewState.showBackButton', false); + } // Should the nav bar be hidden for this view or not? - navBarCtrl.showBar(!$scope.hideNavBar()); + $rootScope.$broadcast('viewState.showNavBar', ($scope.hideNavBar !== 'true') ); + + // 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); + }); // watch for changes in the title - $scope.$watch('title', function(val, oldVal) { - //Don't send in initial value, changeTitle does that - if (val !== oldVal) { - navBarCtrl.setTitle(val); - } + $scope.$watch('title', function(val) { + $scope.$emit('viewState.titleUpdated', $scope); }); }; } @@ -74,13 +277,44 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu }]) +/** +* @private +*/ +.directive('ionNavBackButton', ['$ionicViewService', '$rootScope', + function($ionicViewService, $rootScope) { + + function goBack(e) { + var backView = $ionicViewService.getBackView(); + backView && backView.go(); + e.alreadyHandled = true; + return false; + } + + return { + restrict: 'E', + scope: { + type: '=', + label: '=', + icon: '=' + }, + replace: true, + template: + '', + link: function($scope) { + $scope.goBack = goBack; + } + }; +}]) + /** * @ngdoc directive * @name ionNavView * @module ionic * @restrict E - * @group navigation - * @groupMainItem * @codepen HjnFx * * @description diff --git a/js/ext/angular/src/ionicAngular.js b/js/ext/angular/src/ionicAngular.js index 618852b83e..556d48f9f1 100644 --- a/js/ext/angular/src/ionicAngular.js +++ b/js/ext/angular/src/ionicAngular.js @@ -26,25 +26,26 @@ angular.module('ionic.service', [ // UI specific services and delegates angular.module('ionic.ui.service', [ 'ionic.ui.service.scrollDelegate', - 'ionic.ui.service.slideBoxDelegate' + 'ionic.ui.service.slideBoxDelegate', + 'ionic.ui.service.sideMenuDelegate', ]); angular.module('ionic.ui', [ - 'ionic.ui.checkbox', - 'ionic.ui.content', - 'ionic.ui.header', - 'ionic.ui.list', - 'ionic.ui.navBar', - 'ionic.ui.popup', - 'ionic.ui.radio', - 'ionic.ui.scroll', - 'ionic.ui.sideMenu', - 'ionic.ui.slideBox', - 'ionic.ui.tabs', - 'ionic.ui.toggle', - 'ionic.ui.touch', - 'ionic.ui.viewState' -]); + 'ionic.ui.content', + 'ionic.ui.scroll', + 'ionic.ui.tabs', + 'ionic.ui.viewState', + 'ionic.ui.header', + 'ionic.ui.sideMenu', + 'ionic.ui.slideBox', + 'ionic.ui.list', + 'ionic.ui.checkbox', + 'ionic.ui.toggle', + 'ionic.ui.radio', + 'ionic.ui.touch', + 'ionic.ui.popup' + ]); + angular.module('ionic', [ 'ionic.service', diff --git a/js/ext/angular/src/service/delegates/ionicSideMenuDelegate.js b/js/ext/angular/src/service/delegates/ionicSideMenuDelegate.js new file mode 100644 index 0000000000..9b7610832b --- /dev/null +++ b/js/ext/angular/src/service/delegates/ionicSideMenuDelegate.js @@ -0,0 +1,40 @@ + +(function() { +'use strict'; + +angular.module('ionic.ui.service.sideMenuDelegate', []) + +.factory('$ionicSideMenuDelegate', ['$rootScope', '$timeout', '$q', function($rootScope, $timeout, $q) { + return { + getSideMenuController: function($scope) { + return $scope.sideMenuController; + }, + close: function($scope) { + if($scope.sideMenuController) { + $scope.sideMenuController.close(); + } + }, + toggleLeft: function($scope) { + if($scope.sideMenuController) { + $scope.sideMenuController.toggleLeft(); + } + }, + toggleRight: function($scope) { + if($scope.sideMenuController) { + $scope.sideMenuController.toggleRight(); + } + }, + openLeft: function($scope) { + if($scope.sideMenuController) { + $scope.sideMenuController.openPercentage(100); + } + }, + openRight: function($scope) { + if($scope.sideMenuController) { + $scope.sideMenuController.openPercentage(-100); + } + } + }; +}]); + +})(); diff --git a/js/ext/angular/test/directive/ionicBar.unit.js b/js/ext/angular/test/directive/ionicBar.unit.js index 076e13a8e5..ea6d8e01ef 100644 --- a/js/ext/angular/test/directive/ionicBar.unit.js +++ b/js/ext/angular/test/directive/ionicBar.unit.js @@ -1,53 +1,74 @@ -describe('bar directives', function() { +describe('Ionic Header Bar', function() { + var el, rootScope, compile; + beforeEach(module('ionic')); - angular.forEach([{ - tag: 'ion-header-bar', - element: 'header', - model: 'headerBarController' - }, { - tag: 'ion-footer-bar', - element: 'footer', - model: 'footerBarController' - }], function(data) { - describe(data.tag, function() { + beforeEach(inject(function($animate, $compile, $rootScope) { + compile = $compile; + rootScope = $rootScope; + ionic.requestAnimationFrame = function(cb) { cb(); }; + $animate.enabled(false); + el = null; + })); - function setup(attrs) { - var el; - ionic.views.HeaderBar = function(opts) { - this.opts = opts; - this.align = jasmine.createSpy('align'); - }; - inject(function($compile, $rootScope) { - el = angular.element('<'+data.tag+' '+(attrs||'')+'>'); - el = $compile(el)($rootScope.$new()); - $rootScope.$apply(); - }); - return el; - } - - it('should compile to ' + data.element, function() { - var el = setup(); - expect(el[0].tagName.toLowerCase()).toBe(data.element); - }); - - it('should assign views.HeaderBar to default model', function() { - var el = setup(); - expect(el.scope()[data.model] instanceof ionic.views.HeaderBar).toBe(true); - }); - it('should assign views.HeaderBar to attr model', function() { - var el = setup('model="monkeys"'); - expect(el.scope().monkeys instanceof ionic.views.HeaderBar).toBe(true); - }); - - it('should pass center to views.HeaderBar option by default', function() { - var el = setup(); - expect(el.scope()[data.model].opts.alignTitle).toBe('center'); - }); - it('should pass attr.alignTitle to views.HeaderBar', function() { - var el = setup('align-title="left"'); - expect(el.scope()[data.model].opts.alignTitle).toBe('left'); - }); - }); + it('Should not add title-left or title-right classes when align-title=center', function() { + el = compile('')(rootScope); + var headerView = el.isolateScope().headerBarView; + var title = angular.element(headerView.el.querySelector('.title')); + expect(title.hasClass('title-left')).not.toEqual(true); + expect(title.hasClass('title-right')).not.toEqual(true); }); + + it('Should add title-left class when align-title=left', inject(function($animate) { + el = compile('')(rootScope); + rootScope.$apply(); + var headerView = el.isolateScope().headerBarView; + var title = angular.element(headerView.el.querySelector('.title')); + expect(title.hasClass('title-left')).toEqual(true); + })); + + it('Should add title-right class when align-title=right', function() { + el = compile('')(rootScope); + rootScope.$apply(); + var headerView = el.isolateScope().headerBarView; + var title = angular.element(headerView.el.querySelector('.title')); + expect(title.hasClass('title-right')).toEqual(true); + }); + + it('Should re-align the title when leftButtons change', function() { + rootScope.leftButtons = []; + el = compile('')(rootScope); + var headerView = el.isolateScope().headerBarView; + + //trigger initial align() + rootScope.$apply(); + + spyOn(headerView, 'align'); + + var button = { content: '' }; + rootScope.leftButtons.push(button); + rootScope.$apply(); + + expect(headerView.align).toHaveBeenCalled(); + }); + + it('Should re-align the title when rightButtons change', function() { + rootScope.rightButtons = []; + el = compile('')(rootScope); + var headerView = el.isolateScope().headerBarView; + + //trigger initial align() + rootScope.$apply(); + + spyOn(headerView, 'align'); + + var button = { content: '' }; + rootScope.rightButtons.push(button); + rootScope.$apply(); + + expect(headerView.align).toHaveBeenCalled(); + }); + + + }); diff --git a/js/ext/angular/test/directive/ionicNavBackButton.unit.js b/js/ext/angular/test/directive/ionicNavBackButton.unit.js index 710e083ff3..e69de29bb2 100644 --- a/js/ext/angular/test/directive/ionicNavBackButton.unit.js +++ b/js/ext/angular/test/directive/ionicNavBackButton.unit.js @@ -1,71 +0,0 @@ -describe('ionNavBackButton directive', function() { - beforeEach(module('ionic')); - - function setup(attr, content) { - var el; - inject(function($compile, $rootScope) { - el = angular.element(''+(content||'')+''); - el.data('$ionNavBarController', { - back: jasmine.createSpy('back'), - }); - el = $compile(el)($rootScope.$new()); - $rootScope.$apply(); - }); - return el; - } - - it('should error without a parent ionNavBar', inject(function($compile, $rootScope) { - expect(function() { - $compile('')($rootScope); - }).toThrow(); - })); - - it('should should have class', function() { - var el = setup(); - expect(el.hasClass('button back-button')).toBe(true); - }); - - it('should set hasBackButton through historyChange event', function() { - var el = setup(); - expect(el.scope().hasBackButton).toBeFalsy(); - el.scope().$parent.$broadcast('$viewHistory.historyChange', {showBack: true}); - expect(el.scope().hasBackButton).toBe(true); - el.scope().$parent.$broadcast('$viewHistory.historyChange', {showBack: false}); - expect(el.scope().hasBackButton).toBe(false); - }); - - it('should hide based on backButtonShown && hasBackButton', function() { - var el = setup(); - expect(el.hasClass('hide')).toBe(true); - el.scope().$apply('backButtonShown = true; hasBackButton = true'); - expect(el.hasClass('hide')).toBe(false); - el.scope().$apply('backButtonShown = false; hasBackButton = true'); - expect(el.hasClass('hide')).toBe(true); - el.scope().$apply('backButtonShown = true; hasBackButton = false'); - expect(el.hasClass('hide')).toBe(true); - el.scope().$apply('backButtonShown = true; hasBackButton = true'); - expect(el.hasClass('hide')).toBe(false); - }); - - it('should transclude content', function() { - var el = setup('', 'content {{1+2}}'); - expect(el.text().trim()).toBe('content 3'); - expect(el.children().eq(0)[0].tagName.toLowerCase()).toBe('b'); - }); - - it('should $navBack on click by default', function() { - var el = setup(); - el.scope().$navBack = jasmine.createSpy('$navBack'); - el.triggerHandler('click'); - expect(el.scope().$navBack).toHaveBeenCalled(); - }); - - it('should do ngClick expression if defined', function() { - var el = setup('ng-click="doSomething()"'); - el.scope().$navBack = jasmine.createSpy('$navBack'); - el.scope().doSomething = jasmine.createSpy('doSomething'); - el.triggerHandler('click'); - expect(el.scope().$navBack).not.toHaveBeenCalled(); - expect(el.scope().doSomething).toHaveBeenCalled(); - }); -}); diff --git a/js/ext/angular/test/directive/ionicNavBar.unit.js b/js/ext/angular/test/directive/ionicNavBar.unit.js deleted file mode 100644 index f0cce79f1b..0000000000 --- a/js/ext/angular/test/directive/ionicNavBar.unit.js +++ /dev/null @@ -1,254 +0,0 @@ -describe('ionNavBar', function() { - describe('$ionicNavBar controller', function() { - - var backView = { go: jasmine.createSpy('backViewGo') }; - beforeEach(module('ionic.ui.navBar', function($provide) { - $provide.value('$ionicViewService', { - getBackView: jasmine.createSpy('getBackView').andCallFake(function() { - return backView; - }) - }); - })); - - var parent, el, $scope; - function setup(locals) { - parent = angular.element('
'); - var ctrl; - inject(function($controller, $rootScope) { - el = angular.element('
' + - '
' + - '

' + - '
' + - '
'); - parent.append(el); - $scope = $rootScope.$new(); - - ctrl = $controller('$ionicNavBar', { - $element: el, - $scope: $scope - }); - ctrl._headerBarView = { align: jasmine.createSpy('align') }; - }); - return ctrl; - } - - it('should set controller-data on parent as well for easier access', function() { - var ctrl = setup(); - expect(parent.data('$ionNavBarController')).toBe(ctrl); - }); - - it('should expose leftButtonsElement', function() { - var ctrl = setup(); - expect(ctrl.leftButtonsElement.hasClass('left-buttons')).toBe(true); - }); - it('should expose rightButtonsElement', function() { - var ctrl = setup(); - expect(ctrl.rightButtonsElement.hasClass('right-buttons')).toBe(true); - }); - - it('should go back', inject(function($ionicViewService) { - var ctrl = setup(); - var e = { alreadyHandled: false }; - ctrl.back(e); - expect($ionicViewService.getBackView).toHaveBeenCalled(); - expect(backView.go).toHaveBeenCalled(); - expect(e.alreadyHandled).toBe(true); - })); - - it('should align', function() { - var ctrl = setup(); - expect($scope.backButtonShown).toBeUndefined(); - ctrl.showBackButton(true); - expect($scope.backButtonShown).toBe(true); - ctrl.showBackButton(false); - expect($scope.backButtonShown).toBe(false); - }); - - it('should showBackButton', function() { - var ctrl = setup(); - expect($scope.backButtonShown).toBeUndefined(); - ctrl.showBackButton(true); - expect($scope.backButtonShown).toBe(true); - ctrl.showBackButton(false); - expect($scope.backButtonShown).toBe(false); - }); - - it('should showBar', function() { - var ctrl = setup(); - expect($scope.isInvisible).toBeUndefined(); - ctrl.showBar(true); - expect($scope.isInvisible).toBe(false); - ctrl.showBar(false); - expect($scope.isInvisible).toBe(true); - }); - - it('should setTitle', function() { - var ctrl = setup(); - expect($scope.title).toBeUndefined(); - ctrl.setTitle('foo'); - expect($scope.title).toBe('foo'); - ctrl.setTitle(null); - expect($scope.title).toBe(''); - }) - - describe('changeTitle', function() { - var ctrl; - beforeEach(function() { - ctrl = setup(); - ctrl._animateTitles = jasmine.createSpy('animateTitles'); - }); - - it('should do nothing if title is same', function() { - ctrl.setTitle('123'); - expect($scope.title).toBe('123'); - expect(ctrl.changeTitle('123')).toBe(false); - }); - - it('should set isReverse', function() { - expect($scope.isReverse).toBeFalsy(); - ctrl.changeTitle('foo', 'back'); - expect($scope.isReverse).toBe(true); - ctrl.changeTitle('bar', 'this is not back'); - expect($scope.isReverse).toBe(false); - }); - - it('should set shouldAnimate', function() { - expect($scope.shouldAnimate).toBeFalsy(); - ctrl.changeTitle('foo', 'someDirection'); - expect($scope.shouldAnimate).toBe(true); - ctrl.changeTitle('bar', ''); - expect($scope.shouldAnimate).toBe(false); - }); - - it('should set title & oldTitle', function() { - expect($scope.oldTitle).toBeFalsy(); - ctrl.setTitle('title1'); - ctrl.changeTitle('title2'); - expect($scope.oldTitle).toBe('title1'); - expect($scope.title).toBe('title2'); - ctrl.changeTitle('title3'); - expect($scope.oldTitle).toBe('title2'); - expect($scope.title).toBe('title3'); - }); - - it('should only align if no navDirection', function() { - expect(ctrl._headerBarView.align).not.toHaveBeenCalled(); - ctrl.changeTitle('title1', null); - expect(ctrl._headerBarView.align).toHaveBeenCalled(); - expect(ctrl._animateTitles).not.toHaveBeenCalled(); - }); - - it('should animateTitles if a navDirection given', function() { - expect(ctrl._animateTitles).not.toHaveBeenCalled(); - ctrl.changeTitle('title1', 'back'); - expect(ctrl._headerBarView.align).not.toHaveBeenCalled(); - expect(ctrl._animateTitles).toHaveBeenCalled(); - }); - }); - - describe('animateTitles', function() { - var ctrl; - beforeEach(function() { - ctrl = setup(); - //Add an extra h1 just so we can test that it gets removed - el.append('

'); - ionic.requestAnimationFrame = function(cb) { cb(); }; - }); - - it('before raf should replace title with oldTitle', function() { - //Make raf do nothing so we can test what happens before it - ionic.requestAnimationFrame = function(){}; - - $scope.oldTitle = 'someTitle'; - ctrl._animateTitles(); - var titles = el[0].querySelectorAll('.title'); - expect(titles.length).toBe(2); - expect(titles[0].getAttribute('ng-bind-html')).toEqual('oldTitle'); - expect(titles[1].classList.contains('filler-element')).toBe(true); - }); - - it('after raf should have changed titles & cleaned up', inject(function($animate) { - var enterCallback; - $animate.leave = jasmine.createSpy('leave'); - $animate.enter = jasmine.createSpy('enter').andCallFake(function(child,parent,_,cb) { - parent.append(child); - enterCallback = cb; - }); - spyOn($scope, '$digest'); - ctrl._animateTitles(); - var oldTitle = el[0].querySelector('[ng-bind-html="oldTitle"]'); - var title = el[0].querySelector('[ng-bind-html="title"]'); - expect($animate.leave.mostRecentCall.args[0][0]).toBe(oldTitle); - expect($animate.enter.mostRecentCall.args[0][0]).toBe(title); - expect(el[0].querySelector('h1.filler-element')).toBe(null); - - expect($scope.$digest).toHaveBeenCalled(); - expect(title.classList.contains('invisible')).toBe(false); - })); - - }); - }); - - describe('ionNavBar directive', function() { - beforeEach(module('ionic.ui.navBar')); - function setup(attrs, content) { - var el; - inject(function($compile, $rootScope) { - el = $compile(''+(content||'')+'')($rootScope.$new()); - $rootScope.$apply(); - }); - return el; - } - - it('should prepend-transclude content', function() { - var el = setup('', 'super content {{4}}'); - expect(el.children().eq(0).html()).toBe('super content 4'); - }); - - it('should assign $scope.navBarController by default', function() { - var el = setup(); - expect(el.controller('ionNavBar')).toBeTruthy(); //sanity - expect(el.scope().navBarController).toBe(el.controller('ionNavBar')); - }); - - it('should assign $scope.navBarController to attr.model if set', function() { - var el = setup('model="theNavBarCtrl"'); - expect(el.controller('ionNavBar')).toBeTruthy(); - expect(el.scope().theNavBarCtrl).toBe(el.controller('ionNavBar')); - }); - - it('should have isInvisible class (default true)', function() { - var el = setup(); - expect(el.hasClass('invisible')).toBe(true); - el.isolateScope().$apply('isInvisible = false'); - expect(el.hasClass('invisible')).toBe(false); - el.isolateScope().$apply('isInvisible = true'); - expect(el.hasClass('invisible')).toBe(true); - }); - - it('should have animation class', function() { - var el = setup('animation="my-anim"'); - expect(el.hasClass('my-anim')).toBe(true); - el.isolateScope().$apply('shouldAnimate = false'); - expect(el.hasClass('my-anim')).toBe(false); - el.isolateScope().$apply('shouldAnimate = true'); - expect(el.hasClass('my-anim')).toBe(true); - }); - - it('should have reverse class', function() { - var el = setup(); - expect(el.hasClass('reverse')).toBe(false); - el.isolateScope().$apply('isReverse = true'); - expect(el.hasClass('reverse')).toBe(true); - el.isolateScope().$apply('isReverse = false'); - expect(el.hasClass('reverse')).toBe(false); - }); - - it('should have type class', function() { - var el = setup(); - expect(el.hasClass('superbar')).toBe(false); - el.isolateScope().$apply('type = "superbar"'); - expect(el.hasClass('superbar')).toBe(true); - }); - }); -}); diff --git a/js/ext/angular/test/directive/ionicNavButtons.js b/js/ext/angular/test/directive/ionicNavButtons.js deleted file mode 100644 index 89c1fe62bf..0000000000 --- a/js/ext/angular/test/directive/ionicNavButtons.js +++ /dev/null @@ -1,67 +0,0 @@ -describe('ionNavButtons directive', function() { - - beforeEach(module('ionic.ui.navBar')); - function setup(attrs, contents) { - var el; - inject(function($compile, $rootScope) { - el = angular.element(''+(contents||'')+''); - el.data('$ionNavBarController', { - leftButtonsElement: angular.element('
'), - rightButtonsElement: angular.element('
') - }); - el = $compile(el)($rootScope.$new()); - $rootScope.$apply(); - }); - return el; - } - - it('should error without parent navBar', inject(function($compile, $rootScope) { - expect(function() { - $compile('')($rootScope.$new()); - }).toThrow(); - })); - - it('should make the container element end up display:none', function() { - var el = setup(); - expect(el.css('display')).toBe('none'); - }); - - it('should transclude contents into left', function() { - var el = setup('side="left" ng-init="items=[1,2]"', ''); - var leftButtons = el.controller('ionNavBar').leftButtonsElement.children(); - expect(leftButtons.text().trim()).toEqual('12'); - el.scope().$apply('items=[1,3,5]'); - expect(leftButtons.text().trim()).toEqual('135'); - }); - - it('should transclude contents into right', function() { - var el = setup('side="right" ng-init="items=[1,2]"', ''); - var rightButtons = el.controller('ionNavBar').rightButtonsElement.children(); - expect(rightButtons.text().trim()).toEqual('12'); - el.scope().$apply('items=[1,3,5]'); - expect(rightButtons.text().trim()).toEqual('135'); - }); - - it('left should destroy contents on element destroy', inject(function($animate) { - spyOn($animate, 'leave'); - - var el = setup('side="left" ng-init="items=[1,2]"', ''); - var leftButtons = el.controller('ionNavBar').leftButtonsElement.children(); - expect(leftButtons.text().trim()).toEqual('12'); - el.remove(); - expect($animate.leave).toHaveBeenCalled(); - expect($animate.leave.mostRecentCall.args[0][0]).toBe(leftButtons[0]); - })); - - it('right should destroy contents on element destroy', inject(function($animate) { - spyOn($animate, 'leave'); - - var el = setup('side="right" ng-init="items=[1,2]"', ''); - var rightButtons = el.controller('ionNavBar').rightButtonsElement.children(); - expect(rightButtons.text().trim()).toEqual('12'); - el.remove(); - expect($animate.leave).toHaveBeenCalled(); - expect($animate.leave.mostRecentCall.args[0][0]).toBe(rightButtons[0]); - })); - -}); diff --git a/js/ext/angular/test/directive/ionicView.unit.js b/js/ext/angular/test/directive/ionicView.unit.js index b65d749985..1bbd060934 100644 --- a/js/ext/angular/test/directive/ionicView.unit.js +++ b/js/ext/angular/test/directive/ionicView.unit.js @@ -1,63 +1,263 @@ 'use strict'; -describe('ionView directive', function() { +describe('Ionic View', function() { + var compile, scope, listElement, listCtrl; + beforeEach(module('ionic.ui.viewState')); - function setup(attrs, scopeProps, content) { - var el; - inject(function($compile, $rootScope) { - var scope = angular.extend($rootScope.$new(), scopeProps || {}); - - el = angular.element(''); - el.data('$ionNavBarController', { - changeTitle: jasmine.createSpy('changeTitle'), - setTitle: jasmine.createSpy('setTitle'), - showBackButton: jasmine.createSpy('showBackButton'), - showBar: jasmine.createSpy('showBar') - }); - content && el.html(content); - - el = $compile(el)(scope); - $rootScope.$apply(); - }); - return el; - } - - it('should remove title & add pane, even with no navbar', inject(function($compile, $rootScope) { - var el = $compile('')($rootScope.$new()); - $rootScope.$apply(); - expect(el.hasClass('pane')).toBe(true); - expect(el[0].getAttribute('title')).toBe(null); + beforeEach(inject(function($compile, $rootScope, $controller) { + compile = $compile; + scope = $rootScope; })); - it('should have content inside', function() { - var el = setup(null, null, 'some html'); - expect(el.html()).toBe('some html'); + it('should init a view', function() { + var element = compile('me view')(scope); + expect(element.html()).toEqual('me view'); }); - it('should changeTitle with a navDirection', function() { - var el = setup('title="Hi, {{1}}!"', {$navDirection: 'foo'}); - expect(el.controller('ionNavBar').changeTitle).toHaveBeenCalledWith('Hi, 1!', 'foo'); + it('should add pane classname and remove title from view', function() { + var element = compile('')(scope); + expect(element.attr('title')).toBeUndefined(); + expect(element.hasClass('pane')).toEqual(true); }); - it('should showBackButten depending on what is given', function() { - var el = setup(); - expect(el.controller('ionNavBar').showBackButton).toHaveBeenCalledWith(true); - el = setup('hide-back-button="true"'); - expect(el.controller('ionNavBar').showBackButton).toHaveBeenCalledWith(false); + it('should broacast view enter on link', function() { + spyOn(scope, '$broadcast'); + var element = compile('')(scope); + expect(scope.$broadcast).toHaveBeenCalledWith('viewState.viewEnter', { title: 'Me Title', navDirection: undefined }); + + scope.$navDirection = 'forward'; + element = compile('')(scope); + expect(scope.$broadcast).toHaveBeenCalledWith('viewState.viewEnter', { title: 'Me Title', navDirection: 'forward' }); }); - it('should showBar depending on what is given', function() { - var el = setup(); - expect(el.controller('ionNavBar').showBar).toHaveBeenCalledWith(true); - var el = setup('hide-nav-bar="true"'); - expect(el.controller('ionNavBar').showBar).toHaveBeenCalledWith(false); + it('should set hide back button', function() { + spyOn(scope, '$broadcast'); + + var element = compile('')(scope); + var viewScope = element.isolateScope(); + expect(viewScope.hideBackButton).toBeUndefined(); + expect(scope.$broadcast).not.toHaveBeenCalledWith('viewState.showBackButton', false); + + element = compile('')(scope); + viewScope = element.isolateScope(); + expect(viewScope.hideBackButton).toEqual(true); + expect(scope.$broadcast).toHaveBeenCalledWith('viewState.showBackButton', false); }); - it('should setTitle on change', function() { - var el = setup('', {title: 'foo'}); - expect(el.controller('ionNavBar').setTitle).not.toHaveBeenCalled(); - el.isolateScope().$apply('title = "bar"'); - expect(el.controller('ionNavBar').setTitle).toHaveBeenCalledWith('bar'); + it('should add/remove back button based on events', function() { + var element = compile('')(scope); + scope.$apply(); + function backButton() { + return angular.element(element[0].querySelector('.back-button')); + }; + expect(backButton().hasClass('hide')).toEqual(true); + + scope.$broadcast('viewState.showBackButton', false); + scope.$apply(); + expect(backButton().hasClass('hide')).toEqual(true); + + scope.$broadcast('viewState.showBackButton', true); + scope.$apply(); + expect(backButton().hasClass('hide')).toEqual(false); + + scope.$broadcast('$viewHistory.historyChange', { showBack: false }); + scope.$apply(); + expect(backButton().hasClass('hide')).toEqual(true); + + scope.$broadcast('$viewHistory.historyChange', { showBack: true }); + scope.$apply(); + expect(backButton().hasClass('hide')).toEqual(false); }); + + it('should show/hide navBar', function() { + var element = compile('')(scope); + scope.$digest(); + expect(element.hasClass('invisible')).toEqual(true); + scope.$broadcast('viewState.showNavBar', true); + scope.$digest(); + expect(element.hasClass('invisible')).toEqual(false); + scope.$broadcast('viewState.showNavBar', false); + scope.$digest(); + expect(element.hasClass('invisible')).toEqual(true); + }); + + it('should have have animateEnabled=true if there is a navDirection and animate isnt false', function() { + var element = compile('')(scope); + scope.$digest(); + scope = element.isolateScope(); + + scope.$broadcast('viewState.viewEnter', {}); + expect(scope.animateEnabled).toBe(false); + + scope.$broadcast('viewState.viewEnter', { navDirection: 'forward' }); + expect(scope.animateEnabled).toBe(true); + + scope.$broadcast('viewState.viewEnter', { navDirection: 'forward', animate: false }); + expect(scope.animateEnabled).toBe(false); + + scope.$broadcast('viewState.viewEnter', { navDirection: 'back', animate: true }); + expect(scope.animateEnabled).toBe(true); + }); + + it('should hide navBar when using view attr', function() { + var element = compile('
')(scope); + scope.$digest(); + var navBar = element.find('header') + expect(navBar.hasClass('invisible')).toEqual(true); + }); + + it('should show and update navBar title when using view attr or events', function() { + scope.viewTitle = 'Title'; + var element = compile('
')(scope); + scope.$digest(); + var navBar = element.find('header'); + var title = navBar.find('h1'); + expect(element.find('header').find('h1').text().trim()).toEqual('Title'); + + scope.viewTitle = 'New Title'; + scope.$digest(); + title = navBar.find('h1'); + expect(title.text().trim()).toEqual('New Title'); + + scope.$broadcast('viewState.titleUpdated', { title: 'Event Title' }); + scope.$digest(); + title = navBar.find('h1'); + expect(title.text().trim()).toEqual('Event Title'); + }); + + it('should show / update navBar left and right buttons when using view attr or events', function() { + scope.leftButtons = [{ + type: 'button', + content: 'Left Button' + }]; + scope.rightButtons = [{ + type: 'button', + content: 'Right Button' + }]; + var element = compile('
')(scope); + scope.$digest(); + + var leftButton = angular.element(element[0].querySelector('.left-buttons')).find('button'); + expect(leftButton.text().trim()).toBe('Left Button'); + var rightButton = angular.element(element[0].querySelector('.right-buttons')).find('button'); + expect(rightButton.text().trim()).toBe('Right Button'); + + scope.leftButtons = [{ + type: 'button', + content: 'New Left Button' + }]; + scope.rightButtons = [{ + type: 'button', + content: 'New Right Button' + }]; + + scope.$digest(); + + leftButton = angular.element(element[0].querySelector('.left-buttons')).find('button'); + expect(leftButton.text().trim()).toBe('New Left Button'); + rightButton = angular.element(element[0].querySelector('.right-buttons')).find('button'); + expect(rightButton.text().trim()).toBe('New Right Button'); + + scope.$broadcast('viewState.leftButtonsChanged', [{ + type: 'button', + content: 'Event Left Button' + }]); + scope.$broadcast('viewState.rightButtonsChanged', [{ + type: 'button', + content: 'Event Right Button' + }]); + + scope.$digest(); + + leftButton = angular.element(element[0].querySelector('.left-buttons')).find('button'); + expect(leftButton.text().trim()).toBe('Event Left Button'); + rightButton = angular.element(element[0].querySelector('.right-buttons')).find('button'); + expect(rightButton.text().trim()).toBe('Event Right Button'); + }); + + it('should show navbar when not using view attr', function() { + var element = compile('
')(scope); + scope.$digest(); + var navBar = element.find('header') + expect(navBar.hasClass('invisible')).toEqual(false); + }); + + it('should set the navBar type', function() { + var element = compile('')(scope); + scope.$digest(); + expect(element.hasClass('bar-positive')).toEqual(true); + }); + + it('should not have the back button if no back button attributes set', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.length).toEqual(0); + }); + + it('should have the back button if back-button-type attributes set', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.length).toEqual(1); + }); + + it('should have the back button if back-button-icon attributes set', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.length).toEqual(1); + }); + + it('should have the back button if back-button-label attributes set', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.length).toEqual(1); + }); + + it('should have the back button if all back button attributes set', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.length).toEqual(1); + }); + + it('should set just a back button icon, no text', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.hasClass('button')).toEqual(true); + expect(backButton.hasClass('button-icon')).toEqual(true); + expect(backButton.hasClass('icon')).toEqual(true); + expect(backButton.hasClass('ion-back')).toEqual(true); + expect(backButton.children().length).toEqual(0); + expect(backButton.text().trim()).toEqual(''); + }); + + it('should set just a back button with only text, button-clear', function() { + var element = compile('')(scope); + scope.$apply(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.hasClass('button')).toEqual(true); + expect(backButton.hasClass('button-clear')).toEqual(true); + expect(backButton.hasClass('icon')).toEqual(false); + expect(backButton.text().trim()).toEqual('Back'); + }); + + it('should set a back button with an icon and text, button-icon', function() { + var element = compile('')(scope); + scope.$digest(); + var backButton = angular.element(element[0].querySelector('.back-button')); + expect(backButton.hasClass('button')).toEqual(true); + expect(backButton.hasClass('button-icon')).toEqual(true); + var icon = backButton.find('i'); + expect(icon.hasClass('icon')).toEqual(true); + expect(backButton.children()[0].tagName.toLowerCase()).toBe('i'); + expect(backButton.children()[0].className).toBe('icon ion-back'); + expect(backButton.text().trim()).toBe('Back'); + }); + }); + diff --git a/test/e2e/viewState/test.html b/test/e2e/viewState/test.html index 63a45f907a..a79a833720 100644 --- a/test/e2e/viewState/test.html +++ b/test/e2e/viewState/test.html @@ -17,25 +17,15 @@
- - Back - - + type="bar-positive" + back-button-type="button-icon" + back-button-label=" Back" + back-button-icon="ion-arrow-left-c">