diff options
40 files changed, 2961 insertions, 2191 deletions
| diff --git a/Gruntfile.js b/Gruntfile.js index 264fe874..6d0395fb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -160,6 +160,10 @@ module.exports = function(grunt) {          dest: 'build/angular-resource.js',          src: util.wrap(['src/ngResource/resource.js'], 'module')        }, +      animate: { +        dest: 'build/angular-animate.js', +        src: util.wrap(['src/ngAnimate/animate.js'], 'module') +      },        route: {          dest: 'build/angular-route.js',          src: util.wrap([ @@ -178,6 +182,7 @@ module.exports = function(grunt) {      min: {        angular: 'build/angular.js', +      animate: 'build/angular-animate.js',        cookies: 'build/angular-cookies.js',        loader: 'build/angular-loader.js',        mobile: 'build/angular-mobile.js', diff --git a/angularFiles.js b/angularFiles.js index dc98bcc1..c06f8adc 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -10,8 +10,7 @@ angularFiles = {      'src/auto/injector.js',      'src/ng/anchorScroll.js', -    'src/ng/animation.js', -    'src/ng/animator.js', +    'src/ng/animate.js',      'src/ng/browser.js',      'src/ng/cacheFactory.js',      'src/ng/compile.js', @@ -66,6 +65,7 @@ angularFiles = {    ],    'angularSrcModules': [ +    'src/ngAnimate/animate.js',      'src/ngCookies/cookies.js',      'src/ngResource/resource.js',      'src/ngRoute/routeUtils.js', @@ -107,6 +107,7 @@ angularFiles = {      'test/*.js',      'test/auto/*.js',      'test/ng/**/*.js', +    'test/ngAnimate/*.js',      'test/ngCookies/*.js',      'test/ngResource/*.js',      'test/ngRoute/**/*.js', diff --git a/css/angular.css b/css/angular.css index 0cd9d7dc..6833234f 100644 --- a/css/angular.css +++ b/css/angular.css @@ -8,3 +8,7 @@  ng\:form {    display: block;  } + +.ng-hide { +  display: none; +} diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js index 321118ad..a17c906c 100644 --- a/docs/component-spec/annotationsSpec.js +++ b/docs/component-spec/annotationsSpec.js @@ -67,41 +67,33 @@ describe('Docs Annotations', function() {      var $scope, parent, element, url, window;      beforeEach(function() { -      module(function($provide, $animationProvider) { +      module(function($provide, $animateProvider) {          $provide.value('$window', window = angular.mock.createMockWindow()); -        $animationProvider.register('foldout-enter', function($window) { +        $animateProvider.register('.foldout', function($window) {            return { -            start : function(element, done) { +            enter : function(element, done) {                $window.setTimeout(done, 1000); -            } -          } -        }); -        $animationProvider.register('foldout-hide', function($window) { -          return { -            start : function(element, done) { +            }, +            show : function(element, done) {                $window.setTimeout(done, 500); -            } -          } -        }); -        $animationProvider.register('foldout-show', function($window) { -          return { -            start : function(element, done) { +            }, +            hide : function(element, done) {                $window.setTimeout(done, 200);              }            }          });        }); -      inject(function($rootScope, $compile, $templateCache, $rootElement, $animator) { -        $animator.enabled(true); +      inject(function($rootScope, $compile, $templateCache, $rootElement, $animate) { +        $animate.enabled(true);          url = '/page.html';          $scope = $rootScope.$new();          parent = angular.element('<div class="parent"></div>'); -        element = angular.element('<div data-url="' + url + '" foldout></div>');          //we're injecting the element to the $rootElement since the changes in -        //$animator only detect and perform animations if the root element has +        //$animate only detect and perform animations if the root element has          //animations enabled. If the element is not apart of the DOM          //then animations are skipped. +        element = angular.element('<div data-url="' + url + '" class="foldout" foldout></div>');          parent.append(element);          $rootElement.append(parent);          body.append($rootElement); @@ -142,16 +134,19 @@ describe('Docs Annotations', function() {        $httpBackend.flush();        window.setTimeout.expect(1).process();        window.setTimeout.expect(1000).process(); +      window.setTimeout.expect(0).process();        //hide        element.triggerHandler('click');        window.setTimeout.expect(1).process(); -      window.setTimeout.expect(500).process(); +      window.setTimeout.expect(200).process(); +      window.setTimeout.expect(0).process();        //show        element.triggerHandler('click');        window.setTimeout.expect(1).process(); -      window.setTimeout.expect(200).process(); +      window.setTimeout.expect(500).process(); +      window.setTimeout.expect(0).process();      }));    }); @@ -160,7 +155,7 @@ describe('Docs Annotations', function() {      var window, $scope, ctrl;      beforeEach(function() { -      module(function($provide, $animationProvider) { +      module(function($provide, $animateProvider) {          $provide.value('$window', window = angular.mock.createMockWindow());        });        inject(function($rootScope, $controller, $location, $cookies, sections) { diff --git a/docs/components/angular-bootstrap/bootstrap-prettify.js b/docs/components/angular-bootstrap/bootstrap-prettify.js index ad5340e5..fa40c6e7 100644 --- a/docs/components/angular-bootstrap/bootstrap-prettify.js +++ b/docs/components/angular-bootstrap/bootstrap-prettify.js @@ -183,8 +183,8 @@ directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplat  }]; -directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', -                function($templateCache,   $browser,  docsRootScope, $location,   $sniffer) { +directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate', +                function($templateCache,   $browser,  docsRootScope, $location,   $sniffer, $animate) {    return {      terminal: true,      link: function(scope, element, attrs) { @@ -193,6 +193,7 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',            deregisterEmbedRootScope;        modules.push(['$provide', function($provide) { +        $provide.value('$animate', $animate);          $provide.value('$templateCache', $templateCache);          $provide.value('$anchorScroll', angular.noop);          $provide.value('$browser', $browser); diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js index 170e8805..20a5741d 100644 --- a/docs/components/angular-bootstrap/bootstrap.js +++ b/docs/components/angular-bootstrap/bootstrap.js @@ -335,12 +335,11 @@ directive.tabPane = function() {    };  }; -directive.foldout = ['$http', '$animator','$window', function($http, $animator, $window) { +directive.foldout = ['$http', '$animate','$window', function($http, $animate, $window) {    return {      restrict: 'A',      priority : 500,      link: function(scope, element, attrs) { -      var animator = $animator(scope, { ngAnimate: "'foldout'" });        var container, loading, url = attrs.url;        if(/\/build\//.test($window.location.href)) {          url = '/build/docs' + url; @@ -353,7 +352,7 @@ directive.foldout = ['$http', '$animator','$window', function($http, $animator,              loading = true;              var par = element.parent();              container = angular.element('<div class="foldout">loading...</div>'); -            animator.enter(container, null, par); +            $animate.enter(container, null, par);              $http.get(url, { cache : true }).success(function(html) {                loading = false; @@ -367,12 +366,12 @@ directive.foldout = ['$http', '$animator','$window', function($http, $animator,                //avoid showing the element if the user has already closed it                if(container.css('display') == 'block') {                  container.css('display','none'); -                animator.show(container); +                $animate.show(container);                }              });            }            else { -            container.css('display') == 'none' ? animator.show(container) : animator.hide(container); +            container.hasClass('ng-hide') ? $animate.show(container) : $animate.hide(container);            }          });        }); diff --git a/docs/src/example.js b/docs/src/example.js index cdbc24a7..9471b3fa 100644 --- a/docs/src/example.js +++ b/docs/src/example.js @@ -134,7 +134,7 @@ exports.Example.prototype.toHtmlTabs = function() {  exports.Example.prototype.toHtmlEmbed = function() {    var out = []; -  out.push('<div class="well doc-example-live animator-container"'); +  out.push('<div class="well doc-example-live animate-container"');    if(this.animations) {      out.push(" ng-class=\"{'animations-off':animationsOff == true}\"");    } diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index 394510c5..eb26bbf2 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -494,6 +494,19 @@ Doc.prototype = {    html_usage_parameters: function(dom) {      var self = this;      var params = this.param ? this.param : []; +    if(this.animations) { +      dom.h('Animations', this.animations, function(animations){ +        dom.html('<ul>'); +        var animations = animations.split("\n"); +        animations.forEach(function(ani) { +          dom.html('<li>'); +          dom.text(ani); +          dom.html('</li>'); +        }); +        dom.html('</ul>'); +      }); +      dom.html('<a href="api/ngAnimate.$animate">Click here</a> to learn more about the steps involved in the animation.'); +    }      if(params.length > 0) {        dom.html('<h2 id="parameters">Parameters</h2>');        dom.html('<table class="variables-matrix table table-bordered table-striped">'); @@ -538,18 +551,6 @@ Doc.prototype = {        dom.html('</tbody>');        dom.html('</table>');      } -    if(this.animations) { -      dom.h('Animations', this.animations, function(animations){ -        dom.html('<ul>'); -        var animations = animations.split("\n"); -        animations.forEach(function(ani) { -          dom.html('<li>'); -          dom.text(ani); -          dom.html('</li>'); -        }); -        dom.html('</ul>'); -      }); -    }    },    html_usage_returns: function(dom) { @@ -665,48 +666,6 @@ Doc.prototype = {              dom.text('</' + element + '>');            });          } -        if(self.animations) { -          var animations = [], matches = self.animations.split("\n"); -          matches.forEach(function(ani) { -            var name = ani.match(/^\s*(.+?)\s*-/)[1]; -            animations.push(name); -          }); - -          dom.html('with <span id="animations">animations</span>'); -          var comment; -          if(animations.length == 1) { -            comment = 'The ' + animations[0] + ' animation is supported'; -          } -          else { -            var rhs = animations[animations.length-1]; -            var lhs = ''; -            for(var i=0;i<animations.length-1;i++) { -              if(i>0) { -                lhs += ', '; -              } -              lhs += animations[i]; -            } -            comment = 'The ' + lhs + ' and ' + rhs + ' animations are supported'; -          } -          var element = self.element || 'ANY'; -          dom.code(function() { -            dom.text('//' + comment + "\n"); -            dom.text('<' + element + ' '); -            dom.text(dashCase(self.shortName)); -            renderParams('\n     ', '="', '"', true); -            dom.text(' ng-animate="{'); -            animations.forEach(function(ani, index) { -              if (index) { -                dom.text(', '); -              } -              dom.text(ani + ': \'' + ani + '-animation\''); -            }); -            dom.text('}">\n   ...\n'); -            dom.text('</' + element + '>'); -          }); - -          dom.html('<a href="api/ng.$animator#Methods">Click here</a> to learn more about the steps involved in the animation.'); -        }        }        self.html_usage_directiveInfo(dom);        self.html_usage_parameters(dom); diff --git a/docs/src/templates/css/animations.css b/docs/src/templates/css/animations.css index 2d54bbfb..7324a8a1 100644 --- a/docs/src/templates/css/animations.css +++ b/docs/src/templates/css/animations.css @@ -1,4 +1,4 @@ -.reveal { +.reveal.ng-enter {    -webkit-transition:1s linear all;    -moz-transition:1s linear all;    -o-transition:1s linear all; @@ -6,7 +6,7 @@    opacity:0;  } -.reveal.reveal-active { +.reveal.ng-enter.ng-enter-active {    opacity:1;  } @@ -15,48 +15,45 @@    overflow:hidden;  } -.slide-reveal { +.slide-reveal > .ng-enter {    -webkit-transition:0.5s linear all;    -moz-transition:0.5s linear all;    -o-transition:0.5s linear all;    transition:0.5s linear all; -  opacity:0.5; +  opacity:0.5;    position:relative;    opacity:0;    top:10px;  } -.slide-reveal.slide-reveal-active { +.slide-reveal > .ng-enter.ng-enter-active {    top:0;    opacity:1;  } -.expand-enter { +.expand.ng-enter, +.expand.ng-leave {    -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; - +} +.expand.ng-enter {    opacity:0;    line-height:0;    height:0!important;  } -.expand-enter.expand-enter-active { +.expand.ng-enter.expand.ng-enter-active {    opacity:1;    line-height:20px;    height:20px!important;  } -.expand-leave { -  -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; -  -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; -  -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; -  transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all; - +.expand.ng-leave {    opacity:1;    height:20px;  } -.expand-leave.expand-leave-active { +.expand.ng-leave.expand.ng-leave-active {    opacity:0;    height:0;  } @@ -73,32 +70,36 @@    padding:1em;  } -.animator-container.animations-off * { +.animate-container.animations-off * {    -webkit-transition: none;    -moz-transition: none;    -o-transition: color 0 ease-in; /* opera is special :) */    transition: none;  } -.foldout-show, .foldout-enter, .foldout-hide { +.foldout.ng-enter, +.foldout.ng-hide-add, +.foldout.ng-hide-remove {    -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;    transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;  } -.foldout-show, .foldout-enter { +.foldout.ng-hide-remove, +.foldout.ng-enter {    opacity:0;  } -.foldout-show.foldout-show-active, .foldout-hide.foldout-hide-active { +.foldout.ng-hide-remove.ng-hide-remove-active, +.foldout.ng-enter.ng-enter-active {    opacity:1;  } -.foldout-hide { +.foldout.ng-hide-add {    opacity:1;  } -.foldout-hide.foldout-hide-active { +.foldout.ng-hide-add.ng-hide-active {    opacity:0;  } diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html index 82a5c87e..3f3e83c8 100644 --- a/docs/src/templates/index.html +++ b/docs/src/templates/index.html @@ -43,6 +43,7 @@        addTag('script', {src: path('angular-cookies.js') }, sync);        addTag('script', {src: path('angular-sanitize.js') }, sync);        addTag('script', {src: path('angular-mobile.js') }, sync); +      addTag('script', {src: path('angular-animate.js') }, sync);        addTag('script', {src: 'components/angular-bootstrap.js' }, sync);        addTag('script', {src: 'components/angular-bootstrap-prettify.js' }, sync);        addTag('script', {src: 'components/google-code-prettify.js' }, sync); @@ -201,7 +202,7 @@    </header>   <div id="docs-fold-overlay" ng-show="docs_fold" ng-click="fold(null)"></div> - <div id="docs-fold" ng-show="docs_fold" ng-animate="'fold'"> + <div class="foldout" id="docs-fold" ng-show="docs_fold">     <div id="docs-fold-close" ng-click="fold(null)">       <span class="icon-remove-sign"></span>     </div> @@ -283,21 +284,21 @@                <li class="nav-header section" ng-show="module.directives">                  <a href="{{URL.directive}}" class="guide">directive</a>                </li> -              <li ng-repeat="page in module.directives track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item"> +              <li ng-repeat="page in module.directives track by page.url" ng-class="navClass(page)" class="expand api-list-item">                  <a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>                </li>                <li class="nav-header section" ng-show="module.filters">                  <a href="{{URL.filter}}" class="guide">filter</a>                </li> -              <li ng-repeat="page in module.filters track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item"> +              <li ng-repeat="page in module.filters track by page.url" ng-class="navClass(page)" class="expand api-list-item">                  <a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>                </li>                <li class="nav-header section" ng-show="module.services">                  <a href="{{URL.service}}" class="guide">service</a>                </li> -              <li ng-repeat="service in module.services track by service.instance.url" ng-animate="'expand'" ng-class="navClass(service.instance, service.provider)" class="api-list-item"> +              <li ng-repeat="service in module.services track by service.instance.url" ng-class="navClass(service.instance, service.provider)" class="api-list-item expand">                  <a ng-show="service.provider" class="pull-right" href="{{service.provider.url}}" tabindex="2"><i class="icon-cog"></i></a>                  <a href="{{service.instance.url}}" tabindex="2">{{service.name}}</a>                </li> @@ -305,7 +306,7 @@                <li class="nav-header section" ng-show="module.types">                  <a href="{{URL.type}}" class="guide">Types</a>                </li> -              <li ng-repeat="page in module.types track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item"> +              <li ng-repeat="page in module.types track by page.url" ng-class="navClass(page)" class="expand api-list-item">                  <a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>                </li> @@ -334,7 +335,7 @@          <div id="loading" ng-show="loading">Loading...</div> -        <div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content" ng-animate="{enter: 'slide-reveal'}" ></div> +        <div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>          <div id="disqus" class="disqus">            <h2>Discussion</h2> diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js index 7cac6a9a..05b09571 100644 --- a/docs/src/templates/js/docs.js +++ b/docs/src/templates/js/docs.js @@ -803,7 +803,7 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie  }; -angular.module('docsApp', ['ngResource', 'ngRoute', 'ngCookies', 'ngSanitize', 'bootstrap', 'bootstrapPrettify', 'docsData']). +angular.module('docsApp', ['ngResource', 'ngRoute', 'ngCookies', 'ngSanitize', 'ngAnimate', 'bootstrap', 'bootstrapPrettify', 'docsData']).    config(function($locationProvider) {      $locationProvider.html5Mode(true).hashPrefix('!');    }). diff --git a/karma-docs.conf.js b/karma-docs.conf.js index 65f51fdd..7d21570d 100644 --- a/karma-docs.conf.js +++ b/karma-docs.conf.js @@ -15,6 +15,7 @@ module.exports = function(config) {        'build/angular-mobile.js',        'build/angular-sanitize.js',        'build/angular-route.js', +      'build/angular-animate.js',        'build/docs/components/lunr.js',        'build/docs/components/google-code-prettify.js', diff --git a/src/Angular.js b/src/Angular.js index 4e050a0c..68768d34 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1053,13 +1053,13 @@ function bootstrap(element, modules) {      }]);      modules.unshift('ng');      var injector = createInjector(modules); -    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animator', -       function(scope, element, compile, injector, animator) { +    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', +       function(scope, element, compile, injector, animate) {          scope.$apply(function() {            element.data('$injector', injector);            compile(element)(scope);          }); -        animator.enabled(true); +        animate.enabled(true);        }]      );      return injector; diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 7745cce9..b225fc85 100755 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -106,8 +106,7 @@ function publishExternalAPI(angular){          directive(ngEventDirectives);        $provide.provider({          $anchorScroll: $AnchorScrollProvider, -        $animation: $AnimationProvider, -        $animator: $AnimatorProvider, +        $animate: $AnimateProvider,          $browser: $BrowserProvider,          $cacheFactory: $CacheFactoryProvider,          $controller: $ControllerProvider, diff --git a/src/loader.js b/src/loader.js index 712ff0f7..b28ccee2 100644 --- a/src/loader.js +++ b/src/loader.js @@ -173,24 +173,30 @@ function setupModuleLoader(window) {             * @param {Function} animationFactory Factory function for creating new instance of an animation.             * @description             * -           * Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate} -           * alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives. +           * **NOTE**: animations are take effect only if the **ngAnimate** module is loaded. +           * +           * +           * Defines an animation hook that can be later used with {@link ngAnimate.$animate $animate} service and +           * directives that use this service. +           *             * <pre> -           * module.animation('animation-name', function($inject1, $inject2) { +           * module.animation('.animation-name', function($inject1, $inject2) {             *   return { -           *     //this gets called in preparation to setup an animation -           *     setup : function(element) { ... }, -           * -           *     //this gets called once the animation is run -           *     start : function(element, done, memo) { ... } +           *     eventName : function(element, done) { +           *       //code to run the animation +           *       //once complete, then run done() +           *       return function cancellationFunction(element) { +           *         //code to cancel the animation +           *       } +           *     }             *   }             * })             * </pre>             * -           * See {@link ng.$animationProvider#register $animationProvider.register()} and -           * {@link ng.directive:ngAnimate ngAnimate} for more information. +           * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and +           * {@link ngAnimate ngAnimate module} for more information.             */ -          animation: invokeLater('$animationProvider', 'register'), +          animation: invokeLater('$animateProvider', 'register'),            /**             * @ngdoc method diff --git a/src/ng/animate.js b/src/ng/animate.js new file mode 100644 index 00000000..7e515594 --- /dev/null +++ b/src/ng/animate.js @@ -0,0 +1,112 @@ +'use strict'; + +/** + * @ngdoc object + * @name ng.$animateProvider + * + * @description + * Default implementation of $animate that doesn't perform any animations, instead just synchronously performs DOM + * updates and calls done() callbacks. + * + * In order to enable animations the ngAnimate module has to be loaded. + * + * To see the functional implementation check out src/ngAnimate/animate.js + */ +var $AnimateProvider = ['$provide', function($provide) { + +  this.$$selectors = []; + + +  /** +   * @ngdoc function +   * @name ng.$animateProvider#register +   * @methodOf ng.$animateProvider +   * +   * @description +   * Registers a new injectable animation factory function. The factory function produces the animation object which +   * contains callback functions for each event that is expected to be animated. +   * +   *   * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` must be called once the +   *   element animation is complete. If a function is returned then the animation service will use this function to +   *   cancel the animation whenever a cancel event is triggered. +   * +   * +   *<pre> +   *   return { +     *     eventFn : function(element, done) { +     *       //code to run the animation +     *       //once complete, then run done() +     *       return function cancellationFunction() { +     *         //code to cancel the animation +     *       } +     *     } +     *   } +   *</pre> +   * +   * @param {string} name The name of the animation. +   * @param {function} factory The factory function that will be executed to return the animation object. +   */ +  this.register = function(name, factory) { +    var classes = name.substr(1).split('.'); +    name += '-animation'; +    this.$$selectors.push({ +      selectors : classes, +      name : name +    }); +    $provide.factory(name, factory); +  }; + +  this.$get = function() { +    return { +      enter : function(element, parent, after, done) { +        var afterNode = after && after[after.length - 1]; +        var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; +        // IE does not like undefined so we have to pass null. +        var afterNextSibling = (afterNode && afterNode.nextSibling) || null; +        forEach(element, function(node) { +          parentNode.insertBefore(node, afterNextSibling); +        }); +        (done || noop)(); +      }, + +      leave : function(element, done) { +        element.remove(); +        (done || noop)(); +      }, + +      move : function(element, parent, after, done) { +        // Do not remove element before insert. Removing will cause data associated with the +        // element to be dropped. Insert will implicitly do the remove. +        this.enter(element, parent, after, done); +      }, + +      show : function(element, done) { +        element.removeClass('ng-hide'); +        (done || noop)(); +      }, + +      hide : function(element, done) { +        element.addClass('ng-hide'); +        (done || noop)(); +      }, + +      addClass : function(element, className, done) { +        className = isString(className) ? +                      className : +                      isArray(className) ? className.join(' ') : ''; +        element.addClass(className); +        (done || noop)(); +      }, + +      removeClass : function(element, className, done) { +        className = isString(className) ? +                      className : +                      isArray(className) ? className.join(' ') : ''; +        element.removeClass(className); +        (done || noop)(); +      }, + +      enabled : noop +    }; +  }; +}]; diff --git a/src/ng/animation.js b/src/ng/animation.js deleted file mode 100644 index faed84ca..00000000 --- a/src/ng/animation.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @ngdoc object - * @name ng.$animationProvider - * @description - * - * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside - * of a module. - * - */ -$AnimationProvider.$inject = ['$provide']; -function $AnimationProvider($provide) { -  var suffix = 'Animation'; - -  /** -   * @ngdoc function -   * @name ng.$animation#register -   * @methodOf ng.$animationProvider -   * -   * @description -   * Registers a new injectable animation factory function. The factory function produces the animation object which -   * has these two properties: -   * -   *   * `setup`: `function(Element):*` A function which receives the starting state of the element. The purpose -   *   of this function is to get the element ready for animation. Optionally the function returns an memento which -   *   is passed to the `start` function. -   *   * `start`: `function(Element, doneFunction, *)` The element to animate, the `doneFunction` to be called on -   *   element animation completion, and an optional memento from the `setup` function. -   * -   * @param {string} name The name of the animation. -   * @param {function} factory The factory function that will be executed to return the animation object. -   *  -   */ -  this.register = function(name, factory) { -    $provide.factory(camelCase(name) + suffix, factory); -  }; - -  this.$get = ['$injector', function($injector) { -    /** -     * @ngdoc function -     * @name ng.$animation -     * @function -     * -     * @description -     * The $animation service is used to retrieve any defined animation functions. When executed, the $animation service -     * will return a object that contains the setup and start functions that were defined for the animation. -     * -     * @param {String} name Name of the animation function to retrieve. Animation functions are registered and stored -     *        inside of the AngularJS DI so a call to $animate('custom') is the same as injecting `customAnimation` -     *        via dependency injection. -     * @return {Object} the animation object which contains the `setup` and `start` functions that perform the animation. -     */ -    return function $animation(name) { -      if (name) { -        var animationName = camelCase(name) + suffix; -        if ($injector.has(animationName)) { -          return $injector.get(animationName); -        } -      } -    }; -  }]; -} diff --git a/src/ng/animator.js b/src/ng/animator.js deleted file mode 100644 index a9ea5743..00000000 --- a/src/ng/animator.js +++ /dev/null @@ -1,446 +0,0 @@ -'use strict'; - -// NOTE: this is a pseudo directive. - -/** - * @ngdoc directive - * @name ng.directive:ngAnimate - * - * @description - * The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives. - * It effects how the directive will perform DOM manipulation. This allows for complex animations to take place - * without burdening the directive which uses the animation with animation details. The built in directives - * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive. - * Custom directives can take advantage of animation through {@link ng.$animator $animator service}. - * - * Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives: - * - * | Directive                                                 | Supported Animations                               | - * |---------------------------------------------------------- |----------------------------------------------------| - * | {@link ng.directive:ngRepeat#animations ngRepeat}         | enter, leave and move                              | - * | {@link ngRoute.directive:ngView#animations ngView}        | enter and leave                                    | - * | {@link ng.directive:ngInclude#animations ngInclude}       | enter and leave                                    | - * | {@link ng.directive:ngSwitch#animations ngSwitch}         | enter and leave                                    | - * | {@link ng.directive:ngIf#animations ngIf}                 | enter and leave                                    | - * | {@link ng.directive:ngShow#animations ngShow & ngHide}    | show and hide                                      | - * - * You can find out more information about animations upon visiting each directive page. - * - * Below is an example of a directive that makes use of the ngAnimate attribute: - * - * <pre> - * <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well --> - * <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY> - * - * <!-- you can also use a short hand --> - * //!annotate="animation" ngAnimate|This *expands* to `{ enter: 'animation-enter', leave: 'animation-leave', ...}`</strong> - * <ANY ng-directive ng-animate=" 'animation' "></ANY> - * - * <!-- keep in mind that ng-animate can take expressions --> - * //!annotate="computeCurrentAnimation\(\)" Scope Function|This will be called each time the scope changes... - * <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY> - * </pre> - * - * The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned. - * - * Keep in mind that if an animation is running, no child element of such animation can also be animated. - * - * <h2>CSS-defined Animations</h2> - * By default, ngAnimate attaches two CSS classes per animation event to the DOM element to achieve the animation. - * It is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions as - * well as CSS animations. - * - * The following code below demonstrates how to perform animations using **CSS transitions** with ngAnimate: - * - * <pre> - * <style type="text/css"> - * /* - *  The animate-enter CSS class is the event name that you - *  have provided within the ngAnimate attribute. - * */ - * .animate-enter { - *  -webkit-transition: 1s linear all; /* Safari/Chrome */ - *  -moz-transition: 1s linear all; /* Firefox */ - *  -o-transition: 1s linear all; /* Opera */ - *  transition: 1s linear all; /* IE10+ and Future Browsers */ - * - *  /* The animation preparation code */ - *  opacity: 0; - * } - * - * /* - *  Keep in mind that you want to combine both CSS - *  classes together to avoid any CSS-specificity - *  conflicts - * */ - * .animate-enter.animate-enter-active { - *  /* The animation code itself */ - *  opacity: 1; - * } - * </style> - * - * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div> - * </pre> - * - * The following code below demonstrates how to perform animations using **CSS animations** with ngAnimate: - * - * <pre> - * <style type="text/css"> - * .animate-enter { - *   -webkit-animation: enter_sequence 1s linear; /* Safari/Chrome */ - *   -moz-animation: enter_sequence 1s linear; /* Firefox */ - *   -o-animation: enter_sequence 1s linear; /* Opera */ - *   animation: enter_sequence 1s linear; /* IE10+ and Future Browsers */ - * } - * @-webkit-keyframes enter_sequence { - *   from { opacity:0; } - *   to { opacity:1; } - * } - * @-moz-keyframes enter_sequence { - *   from { opacity:0; } - *   to { opacity:1; } - * } - * @-o-keyframes enter_sequence { - *   from { opacity:0; } - *   to { opacity:1; } - * } - * @keyframes enter_sequence { - *   from { opacity:0; } - *   to { opacity:1; } - * } - * </style> - * - * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div> - * </pre> - * - * ngAnimate will first examine any CSS animation code and then fallback to using CSS transitions. - * - * Upon DOM mutation, the event class is added first, then the browser is allowed to reflow the content and then, - * the active class is added to trigger the animation. The ngAnimate directive will automatically extract the duration - * of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be - * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end - * immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element - * has no CSS transition/animation classes surrounding it. - * - * <h2>JavaScript-defined Animations</h2> - * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations to browsers that do not - * yet support them, then you can make use of JavaScript animations defined inside of your AngularJS module. - * - * <pre> - * var ngModule = angular.module('YourApp', []); - * ngModule.animation('animate-enter', function() { - *   return { - *     setup : function(element) { - *       //prepare the element for animation - *       element.css({ 'opacity': 0 }); - *       var memo = "..."; //this value is passed to the start function - *       return memo; - *     }, - *     start : function(element, done, memo) { - *       //start the animation - *       element.animate({ - *         'opacity' : 1 - *       }, function() { - *         //call when the animation is complete - *         done() - *       }); - *     } - *   } - * }); - * </pre> - * - * As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation - * can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled - * animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're not using - * CSS animations) to animated the element, but it will not attempt to find any CSS3 transition or animation duration/delay values. - * It will instead close off the animation once the provided done function is executed. So it's important that you - * make sure your animations remember to fire off the done function once the animations are complete. - * - * @param {expression} ngAnimate Used to configure the DOM manipulation animations. - * - */ - -var $AnimatorProvider = function() { -  var NG_ANIMATE_CONTROLLER = '$ngAnimateController'; -  var rootAnimateController = {running:true}; - -  this.$get = ['$animation', '$window', '$sniffer', '$rootElement', '$rootScope', -      function($animation, $window, $sniffer, $rootElement, $rootScope) { -    $rootElement.data(NG_ANIMATE_CONTROLLER, rootAnimateController); - -    /** -     * @ngdoc function -     * @name ng.$animator -     * @function -     * -     * @description -     * The $animator.create service provides the DOM manipulation API which is decorated with animations. -     * -     * @param {Scope} scope the scope for the ng-animate. -     * @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are -     *        passed into the linking function of the directive using the `$animator`.) -     * @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods. -     */ -     var AnimatorService = function(scope, attrs) { -        var animator = {}; - -        /** -         * @ngdoc function -         * @name ng.animator#enter -         * @methodOf ng.$animator -         * @function -         * -         * @description -         * Injects the element object into the DOM (inside of the parent element) and then runs the enter animation. -         * -         * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation -         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation -         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation -        */ -        animator.enter = animateActionFactory('enter', insert, noop); - -        /** -         * @ngdoc function -         * @name ng.animator#leave -         * @methodOf ng.$animator -         * @function -         * -         * @description -         * Runs the leave animation operation and, upon completion, removes the element from the DOM. -         * -         * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation -         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation -        */ -        animator.leave = animateActionFactory('leave', noop, remove); - -        /** -         * @ngdoc function -         * @name ng.animator#move -         * @methodOf ng.$animator -         * @function -         * -         * @description -         * Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or -         * add the element directly after the after element if present. Then the move animation will be run. -         * -         * @param {jQuery/jqLite element} element the element that will be the focus of the move animation -         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation -         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation -        */ -        animator.move = animateActionFactory('move', move, noop); - -        /** -         * @ngdoc function -         * @name ng.animator#show -         * @methodOf ng.$animator -         * @function -         * -         * @description -         * Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after. -         * -         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden -        */ -        animator.show = animateActionFactory('show', show, noop); - -        /** -         * @ngdoc function -         * @name ng.animator#hide -         * @methodOf ng.$animator -         * -         * @description -         * Starts the hide animation first and sets the CSS `display` property to `none` upon completion. -         * -         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden -        */ -        animator.hide = animateActionFactory('hide', noop, hide); - -        /** -         * @ngdoc function -         * @name ng.animator#animate -         * @methodOf ng.$animator -         * -         * @description -         * Triggers a custom animation event to be executed on the given element -         * -         * @param {string} event the name of the custom event -         * @param {jQuery/jqLite element} element the element that will be animated -        */ -        animator.animate = function(event, element) { -          animateActionFactory(event, noop, noop)(element); -        } -        return animator; - -        function animateActionFactory(type, beforeFn, afterFn) { -          return function(element, parent, after) { -            var ngAnimateValue = scope.$eval(attrs.ngAnimate); -            var className = ngAnimateValue -                ? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type -                : ''; -            var animationPolyfill = $animation(className); -            var polyfillSetup = animationPolyfill && animationPolyfill.setup; -            var polyfillStart = animationPolyfill && animationPolyfill.start; -            var polyfillCancel = animationPolyfill && animationPolyfill.cancel; - -            if (!className) { -              beforeFn(element, parent, after); -              afterFn(element, parent, after); -            } else { -              var activeClassName = className + '-active'; - -              if (!parent) { -                parent = after ? after.parent() : element.parent(); -              } -              var disabledAnimation = { running : true }; -              if ((!$sniffer.transitions && !polyfillSetup && !polyfillStart) || -                  (parent.inheritedData(NG_ANIMATE_CONTROLLER) || disabledAnimation).running) { -                beforeFn(element, parent, after); -                afterFn(element, parent, after); -                return; -              } - -              var animationData = element.data(NG_ANIMATE_CONTROLLER) || {}; -              if(animationData.running) { -                (polyfillCancel || noop)(element); -                animationData.done(); -              } - -              element.data(NG_ANIMATE_CONTROLLER, {running:true, done:done}); -              element.addClass(className); -              beforeFn(element, parent, after); -              if (element.length == 0) return done(); - -              var memento = (polyfillSetup || noop)(element); - -              // $window.setTimeout(beginAnimation, 0); this was causing the element not to animate -              // keep at 1 for animation dom rerender -              $window.setTimeout(beginAnimation, 1); -            } - -            function parseMaxTime(str) { -              var total = 0, values = isString(str) ? str.split(/\s*,\s*/) : []; -              forEach(values, function(value) { -                total = Math.max(parseFloat(value) || 0, total); -              }); -              return total; -            } - -            function beginAnimation() { -              element.addClass(activeClassName); -              if (polyfillStart) { -                polyfillStart(element, done, memento); -              } else if (isFunction($window.getComputedStyle)) { -                //one day all browsers will have these properties -                var w3cAnimationProp = 'animation'; -                var w3cTransitionProp = 'transition'; - -                //but some still use vendor-prefixed styles -                var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation'; -                var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition'; - -                var durationKey = 'Duration', -                    delayKey = 'Delay', -                    animationIterationCountKey = 'IterationCount', -                    duration = 0; - -                //we want all the styles defined before and after -                var ELEMENT_NODE = 1; -                forEach(element, function(element) { -                  if (element.nodeType == ELEMENT_NODE) { -                    var elementStyles = $window.getComputedStyle(element) || {}; - -                    var transitionDelay     = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + delayKey]), -                                                       parseMaxTime(elementStyles[vendorTransitionProp  + delayKey])); - -                    var animationDelay      = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + delayKey]), -                                                       parseMaxTime(elementStyles[vendorAnimationProp   + delayKey])); - -                    var transitionDuration  = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + durationKey]), -                                                       parseMaxTime(elementStyles[vendorTransitionProp  + durationKey])); - -                    var animationDuration   = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + durationKey]), -                                                       parseMaxTime(elementStyles[vendorAnimationProp   + durationKey])); - -                    if(animationDuration > 0) { -                      animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp    + animationIterationCountKey]) || 0, -                                                   parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0, -                                                   1); -                    } - -                    duration = Math.max(animationDelay  + animationDuration, -                                        transitionDelay + transitionDuration, -                                        duration); -                  } -                }); -                $window.setTimeout(done, duration * 1000); -              } else { -                done(); -              } -            } - -            function done() { -              if(!done.run) { -                done.run = true; -                afterFn(element, parent, after); -                element.removeClass(className); -                element.removeClass(activeClassName); -                element.removeData(NG_ANIMATE_CONTROLLER); -              } -            } -          }; -        } - -        function show(element) { -          element.css('display', ''); -        } - -        function hide(element) { -          element.css('display', 'none'); -        } - -        function insert(element, parent, after) { -          var afterNode = after && after[after.length - 1]; -          var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; -          var afterNextSibling = afterNode && afterNode.nextSibling; -          forEach(element, function(node) { -            if (afterNextSibling) { -              parentNode.insertBefore(node, afterNextSibling); -            } else { -              parentNode.appendChild(node); -            } -          }); -        } - -        function remove(element) { -          element.remove(); -        } - -        function move(element, parent, after) { -          // Do not remove element before insert. Removing will cause data associated with the -          // element to be dropped. Insert will implicitly do the remove. -          insert(element, parent, after); -        } -      }; - -    /** -     * @ngdoc function -     * @name ng.animator#enabled -     * @methodOf ng.$animator -     * @function -     * -     * @param {Boolean=} If provided then set the animation on or off. -     * @return {Boolean} Current animation state. -     * -     * @description -     * Globally enables/disables animations. -     * -    */ -    AnimatorService.enabled = function(value) { -      if (arguments.length) { -        rootAnimateController.running = !value; -      } -      return !rootAnimateController.running; -    }; - -    return AnimatorService; -  }]; -}; diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js index 75e35a1e..a5b2acb6 100644 --- a/src/ng/directive/ngClass.js +++ b/src/ng/directive/ngClass.js @@ -2,59 +2,72 @@  function classDirective(name, selector) {    name = 'ngClass' + name; -  return ngDirective(function(scope, element, attr) { -    var oldVal = undefined; - -    scope.$watch(attr[name], ngClassWatchAction, true); - -    attr.$observe('class', function(value) { -      var ngClass = scope.$eval(attr[name]); -      ngClassWatchAction(ngClass, ngClass); -    }); +  return ['$animate', function($animate) { +    return { +      restrict: 'AC', +      link: function(scope, element, attr) { +        var oldVal = undefined; + +        scope.$watch(attr[name], ngClassWatchAction, true); + +        attr.$observe('class', function(value) { +          var ngClass = scope.$eval(attr[name]); +          ngClassWatchAction(ngClass, ngClass); +        }); + + +        if (name !== 'ngClass') { +          scope.$watch('$index', function($index, old$index) { +            var mod = $index & 1; +            if (mod !== old$index & 1) { +              if (mod === selector) { +                addClass(scope.$eval(attr[name])); +              } else { +                removeClass(scope.$eval(attr[name])); +              } +            } +          }); +        } -    if (name !== 'ngClass') { -      scope.$watch('$index', function($index, old$index) { -        var mod = $index & 1; -        if (mod !== old$index & 1) { -          if (mod === selector) { -            addClass(scope.$eval(attr[name])); -          } else { -            removeClass(scope.$eval(attr[name])); +        function ngClassWatchAction(newVal) { +          if (selector === true || scope.$index % 2 === selector) { +            if (oldVal && !equals(newVal,oldVal)) { +              removeClass(oldVal); +            } +            addClass(newVal);            } +          oldVal = copy(newVal);          } -      }); -    } -    function ngClassWatchAction(newVal) { -      if (selector === true || scope.$index % 2 === selector) { -        if (oldVal && !equals(newVal,oldVal)) { -          removeClass(oldVal); +        function removeClass(classVal) { +          $animate.removeClass(element, flattenClasses(classVal));          } -        addClass(newVal); -      } -      oldVal = copy(newVal); -    } -    function removeClass(classVal) { -      if (isObject(classVal) && !isArray(classVal)) { -        classVal = map(classVal, function(v, k) { if (v) return k }); -      } -      element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); -    } +        function addClass(classVal) { +          $animate.addClass(element, flattenClasses(classVal)); +        } +        function flattenClasses(classVal) { +          if(isArray(classVal)) { +            return classVal.join(' '); +          } else if (isObject(classVal)) { +            var classes = [], i = 0; +            forEach(classVal, function(v, k) { +              if (v) { +                classes.push(k); +              } +            }); +            return classes.join(' '); +          } -    function addClass(classVal) { -      if (isObject(classVal) && !isArray(classVal)) { -        classVal = map(classVal, function(v, k) { if (v) return k }); +          return classVal; +        };        } -      if (classVal) { -        element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); -      } -    } -  }); +    }; +  }];  }  /** @@ -70,6 +83,10 @@ function classDirective(name, selector) {   * When the expression changes, the previously added classes are removed and only then the   * new classes are added.   * + * @animations + * add - happens just before the class is applied to the element + * remove - happens just before the class is removed from the element + *   * @element ANY   * @param {expression} ngClass {@link guide/expression Expression} to eval. The result   *   of the evaluation can be a string representing space delimited class @@ -78,7 +95,7 @@ function classDirective(name, selector) {   *   element.   *   * @example -   <example> +   <example animations="true">       <file name="index.html">        <input type="button" value="set" ng-click="myVar='my-class'">        <input type="button" value="clear" ng-click="myVar=''"> @@ -86,8 +103,23 @@ function classDirective(name, selector) {        <span ng-class="myVar">Sample Text</span>       </file>       <file name="style.css"> -       .my-class { +       .my-class-add, +       .my-class-remove { +         -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +         -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +         -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +         transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +       } + +       .my-class, +       .my-class-add.my-class-add-active {           color: red; +         font-size:3em; +       } + +       .my-class-remove.my-class-remove-active { +         font-size:1.0em; +         color:black;         }       </file>       <file name="scenario.js"> diff --git a/src/ng/directive/ngIf.js b/src/ng/directive/ngIf.js index c8166ee5..9d99d859 100755 --- a/src/ng/directive/ngIf.js +++ b/src/ng/directive/ngIf.js @@ -30,7 +30,7 @@   * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element   * the added class will be lost because the original compiled state is used to regenerate the element.   * - * Additionally, you can provide animations via the ngAnimate attribute to animate the **enter** + * Additionally, you can provide animations via the ngAnimate module to animate the **enter**   * and **leave** effects.   *   * @animations @@ -47,36 +47,32 @@      <file name="index.html">        Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/>        Show when checked: -      <span ng-if="checked" ng-animate="'example'"> +      <span ng-if="checked" class="example-if">          I'm removed when the checkbox is unchecked.        </span>      </file>      <file name="animations.css"> -      .example-leave, .example-enter { +      .example-if.ng-enter, +      .example-if.ng-leave {          -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; -        -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;        } -      .example-enter { +      .example-if.ng-enter, +      .example-if.ng-leave.ng-leave-active {          opacity:0;        } -      .example-enter.example-enter-active { -        opacity:1; -      } -      .example-leave { +      .example-if.ng-enter.ng-enter-active, +      .example-if.ng-leave {          opacity:1;        } -      .example-leave.example-leave-active { -        opacity:0; -      }      </file>    </example>   */ -var ngIfDirective = ['$animator', function($animator) { +var ngIfDirective = ['$animate', function($animate) {    return {      transclude: 'element',      priority: 1000, @@ -84,11 +80,10 @@ var ngIfDirective = ['$animator', function($animator) {      restrict: 'A',      compile: function (element, attr, transclude) {        return function ($scope, $element, $attr) { -        var animate = $animator($scope, $attr);          var childElement, childScope;          $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {            if (childElement) { -            animate.leave(childElement); +            $animate.leave(childElement);              childElement = undefined;            }            if (childScope) { @@ -99,7 +94,7 @@ var ngIfDirective = ['$animator', function($animator) {              childScope = $scope.$new();              transclude(childScope, function (clone) {                childElement = clone; -              animate.enter(clone, $element.parent(), $element); +              $animate.enter(clone, $element.parent(), $element);              });            }          }); diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js index 72b5af08..d5ed1fc5 100644 --- a/src/ng/directive/ngInclude.js +++ b/src/ng/directive/ngInclude.js @@ -23,9 +23,6 @@   *      (e.g.  ngInclude won't work for cross-domain requests on all browsers and for `file://`   *      access on some browsers)   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter** - * and **leave** effects. - *   * @animations   * enter - happens just after the ngInclude contents change and a new DOM element is created and injected into the ngInclude container   * leave - happens just after the ngInclude contents change and just before the former contents are removed from the DOM @@ -143,8 +140,8 @@   * @description   * Emitted every time the ngInclude content is reloaded.   */ -var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator', '$sce', -                  function($http,   $templateCache,   $anchorScroll,   $compile,   $animator,   $sce) { +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce', +                  function($http,   $templateCache,   $anchorScroll,   $compile,   $animate,   $sce) {    return {      restrict: 'ECA',      terminal: true, @@ -154,7 +151,6 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'            autoScrollExp = attr.autoscroll;        return function(scope, element, attr) { -        var animate = $animator(scope, attr);          var changeCounter = 0,              childScope; @@ -163,7 +159,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'              childScope.$destroy();              childScope = null;            } -          animate.leave(element.contents(), element); +          $animate.leave(element.contents());          };          scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { @@ -175,11 +171,11 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'                if (childScope) childScope.$destroy();                childScope = scope.$new(); -              animate.leave(element.contents(), element); +              $animate.leave(element.contents());                var contents = jqLite('<div/>').html(response).contents(); -              animate.enter(contents, element); +              $animate.enter(contents, element);                $compile(contents)(childScope);                if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index e0b2cb38..8f12b7c2 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -20,9 +20,6 @@   * | `$even`   | {@type boolean} | true if the iterator position `$index` is even (otherwise false).           |   * | `$odd`    | {@type boolean} | true if the iterator position `$index` is odd (otherwise false).            |   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**, - * **leave** and **move** effects. - *   *   * # Special repeat start and end points   * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending @@ -131,46 +128,40 @@          I have {{friends.length}} friends. They are:          <input type="search" ng-model="q" placeholder="filter friends..." />          <ul> -          <li ng-repeat="friend in friends | filter:q" -              ng-animate="{enter: 'example-repeat-enter', -                          leave: 'example-repeat-leave', -                          move: 'example-repeat-move'}"> +          <li class="animate-repeat" ng-repeat="friend in friends | filter:q">              [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.            </li>          </ul>        </div>      </file>      <file name="animations.css"> -      .example-repeat-enter, -      .example-repeat-leave, -      .example-repeat-move { +      .animate-repeat {          -webkit-transition:all linear 0.5s;          -moz-transition:all linear 0.5s; -        -ms-transition:all linear 0.5s;          -o-transition:all linear 0.5s;          transition:all linear 0.5s;        } -      .example-repeat-enter { +      .animate-repeat.ng-enter {          line-height:0;          opacity:0;        } -      .example-repeat-enter.example-repeat-enter-active { +      .animate-repeat.ng-enter.ng-enter-active {          line-height:20px;          opacity:1;        } -      .example-repeat-leave { +      .animate-repeat.ng-leave {          opacity:1;          line-height:20px;        } -      .example-repeat-leave.example-repeat-leave-active { +      .animate-repeat.ng-leave.ng-leave-active {          opacity:0;          line-height:0;        } -      .example-repeat-move { } -      .example-repeat-move.example-repeat-move-active { } +      .animate-repeat.ng-move { } +      .animate-repeat.ng-move.ng-move-active { }      </file>      <file name="scenario.js">         it('should render initial data set', function() { @@ -195,7 +186,7 @@        </file>      </example>   */ -var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { +var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {    var NG_REMOVED = '$$NG_REMOVED';    var ngRepeatMinErr = minErr('ngRepeat');    return { @@ -204,7 +195,6 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {      terminal: true,      compile: function(element, attr, linker) {        return function($scope, $element, $attr){ -        var animate = $animator($scope, $attr);          var expression = $attr.ngRepeat;          var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),            trackByExp, trackByExpGetter, trackByIdFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, @@ -316,7 +306,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {            for (key in lastBlockMap) {              if (lastBlockMap.hasOwnProperty(key)) {                block = lastBlockMap[key]; -              animate.leave(block.elements); +              $animate.leave(block.elements);                forEach(block.elements, function(element) { element[NG_REMOVED] = true});                block.scope.$destroy();              } @@ -342,7 +332,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {                  // do nothing                } else {                  // existing item which got moved -                animate.move(block.elements, null, jqLite(previousNode)); +                $animate.move(block.elements, null, jqLite(previousNode));                }                previousNode = block.endNode;              } else { @@ -360,7 +350,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {              if (!block.startNode) {                linker(childScope, function(clone) { -                animate.enter(clone, null, jqLite(previousNode)); +                $animate.enter(clone, null, jqLite(previousNode));                  previousNode = clone;                  block.scope = childScope;                  block.startNode = clone[0]; diff --git a/src/ng/directive/ngShowHide.js b/src/ng/directive/ngShowHide.js index 7ef7008c..bdbcf463 100644 --- a/src/ng/directive/ngShowHide.js +++ b/src/ng/directive/ngShowHide.js @@ -12,8 +12,6 @@   * With ngHide this is the reverse whereas true values cause the element itself to become   * hidden.   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show** - * and **hide** effects.   *   * @animations   * show - happens after the ngShow expression evaluates to a truthy value and the contents are set to visible @@ -29,36 +27,37 @@        Click me: <input type="checkbox" ng-model="checked"><br/>        <div>          Show: -        <span class="check-element" -              ng-show="checked" -              ng-animate="{show: 'example-show', hide: 'example-hide'}"> +        <span class="check-element example-show-hide" ng-show="checked">            <span class="icon-thumbs-up"></span> I show up when your checkbox is checked.          </span>        </div>        <div>          Hide: -        <span class="check-element" -              ng-hide="checked" -              ng-animate="{show: 'example-show', hide: 'example-hide'}"> +        <span class="check-element example-show-hide" ng-hide="checked">            <span class="icon-thumbs-down"></span> I hide when your checkbox is checked.          </span>        </div>      </file>      <file name="animations.css"> -      .example-show, .example-hide { +      .example-show-hide {          -webkit-transition:all linear 0.5s;          -moz-transition:all linear 0.5s;          -ms-transition:all linear 0.5s;          -o-transition:all linear 0.5s;          transition:all linear 0.5s; +        display:block; +      } +      .example-show-hide.ng-hide { +        display:none;        } -      .example-show { +      .example-show-hide.ng-hide-remove { +        display:block;          line-height:0;          opacity:0;          padding:0 10px;        } -      .example-show-active.example-show-active { +      .example-show-hide.ng-hide-remove.ng-hide-remove-active {          line-height:20px;          opacity:1;          padding:10px; @@ -66,14 +65,14 @@          background:white;        } -      .example-hide { +      .example-show-hide.ng-hide-add {          line-height:20px;          opacity:1;          padding:10px;          border:1px solid black;          background:white;        } -      .example-hide-active.example-hide-active { +      .example-show-hide.ng-hide-add.ng-hide-add-active {          line-height:0;          opacity:0;          padding:0 10px; @@ -98,12 +97,10 @@      </file>    </example>   */ -//TODO(misko): refactor to remove element from the DOM -var ngShowDirective = ['$animator', function($animator) { +var ngShowDirective = ['$animate', function($animate) {    return function(scope, element, attr) { -    var animate = $animator(scope, attr);      scope.$watch(attr.ngShow, function ngShowWatchAction(value){ -      animate[toBoolean(value) ? 'show' : 'hide'](element); +      $animate[toBoolean(value) ? 'show' : 'hide'](element);      });    };  }]; @@ -121,9 +118,6 @@ var ngShowDirective = ['$animator', function($animator) {   * With ngHide this is the reverse whereas true values cause the element itself to become   * hidden.   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show** - * and **hide** effects. - *   * @animations   * show - happens after the ngHide expression evaluates to a non truthy value and the contents are set to visible   * hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden @@ -138,36 +132,36 @@ var ngShowDirective = ['$animator', function($animator) {        Click me: <input type="checkbox" ng-model="checked"><br/>        <div>          Show: -        <span class="check-element" -              ng-show="checked" -              ng-animate="{show: 'example-show', hide: 'example-hide'}"> +        <span class="check-element example-show-hide" ng-show="checked">            <span class="icon-thumbs-up"></span> I show up when your checkbox is checked.          </span>        </div>        <div>          Hide: -        <span class="check-element" -              ng-hide="checked" -              ng-animate="{show: 'example-show', hide: 'example-hide'}"> +        <span class="check-element example-show-hide" ng-hide="checked">            <span class="icon-thumbs-down"></span> I hide when your checkbox is checked.          </span>        </div>      </file>      <file name="animations.css"> -      .example-show, .example-hide { +      .example-show-hide {          -webkit-transition:all linear 0.5s;          -moz-transition:all linear 0.5s; -        -ms-transition:all linear 0.5s;          -o-transition:all linear 0.5s;          transition:all linear 0.5s; +        display:block; +      } +      .example-show-hide.ng-hide { +        display:none;        } -      .example-show { +      .example-show-hide.ng-hide-remove { +        display:block;          line-height:0;          opacity:0;          padding:0 10px;        } -      .example-show.example-show-active { +      .example-show-hide.ng-hide-remove.ng-hide-remove-active {          line-height:20px;          opacity:1;          padding:10px; @@ -175,14 +169,14 @@ var ngShowDirective = ['$animator', function($animator) {          background:white;        } -      .example-hide { +      .example-show-hide.ng-hide-add {          line-height:20px;          opacity:1;          padding:10px;          border:1px solid black;          background:white;        } -      .example-hide.example-hide-active { +      .example-show-hide.ng-hide-add.ng-hide-add-active {          line-height:0;          opacity:0;          padding:0 10px; @@ -207,12 +201,10 @@ var ngShowDirective = ['$animator', function($animator) {      </file>    </example>   */ -//TODO(misko): refactor to remove element from the DOM -var ngHideDirective = ['$animator', function($animator) { +var ngHideDirective = ['$animate', function($animate) {    return function(scope, element, attr) { -    var animate = $animator(scope, attr);      scope.$watch(attr.ngHide, function ngHideWatchAction(value){ -      animate[toBoolean(value) ? 'hide' : 'show'](element); +      $animate[toBoolean(value) ? 'hide' : 'show'](element);      });    };  }]; diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js index f36e651c..38a123a2 100644 --- a/src/ng/directive/ngSwitch.js +++ b/src/ng/directive/ngSwitch.js @@ -19,9 +19,6 @@   * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default   * attribute is displayed.   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter** - * and **leave** effects. - *   * @animations   * enter - happens after the ngSwtich contents change and the matched child element is placed inside the container   * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM @@ -55,9 +52,8 @@          <tt>selection={{selection}}</tt>          <hr/>          <div -          class="example-animate-container" -          ng-switch on="selection" -          ng-animate="{enter: 'example-enter', leave: 'example-leave'}"> +          class="example-animate-container animate-switch" +          ng-switch on="selection">              <div ng-switch-when="settings">Settings Div</div>              <div ng-switch-when="home">Home Span</div>              <div ng-switch-default>default</div> @@ -71,10 +67,9 @@        }      </file>      <file name="animations.css"> -      .example-leave, .example-enter { +      .animate-switch > * {          -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; -        -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;          transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; @@ -90,17 +85,17 @@          padding:10px;        } -      .example-enter { +      .animate-switch > .ng-enter {          top:-50px;        } -      .example-enter.example-enter-active { +      .animate-switch > .ng-enter.ng-enter-active {          top:0;        } -      .example-leave { +      .animate-switch > .ng-leave {          top:0;        } -      .example-leave.example-leave-active { +      .animate-switch > .ng-leave.ng-leave-active {          top:50px;        }      </file> @@ -119,7 +114,7 @@      </file>    </example>   */ -var ngSwitchDirective = ['$animator', function($animator) { +var ngSwitchDirective = ['$animate', function($animate) {    return {      restrict: 'EA',      require: 'ngSwitch', @@ -129,7 +124,6 @@ var ngSwitchDirective = ['$animator', function($animator) {       this.cases = {};      }],      link: function(scope, element, attr, ngSwitchController) { -      var animate = $animator(scope, attr);        var watchExpr = attr.ngSwitch || attr.on,            selectedTranscludes,            selectedElements, @@ -138,7 +132,7 @@ var ngSwitchDirective = ['$animator', function($animator) {        scope.$watch(watchExpr, function ngSwitchWatchAction(value) {          for (var i= 0, ii=selectedScopes.length; i<ii; i++) {            selectedScopes[i].$destroy(); -          animate.leave(selectedElements[i]); +          $animate.leave(selectedElements[i]);          }          selectedElements = []; @@ -153,7 +147,7 @@ var ngSwitchDirective = ['$animator', function($animator) {                var anchor = selectedTransclude.element;                selectedElements.push(caseElement); -              animate.enter(caseElement, anchor.parent(), anchor); +              $animate.enter(caseElement, anchor.parent(), anchor);              });            });          } diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js new file mode 100644 index 00000000..ab7a19b0 --- /dev/null +++ b/src/ngAnimate/animate.js @@ -0,0 +1,714 @@ +/** + * @ngdoc overview + * @name ngAnimate + * @description + * + * ngAnimate + * ========= + * + * The ngAnimate module is an optional module that comes packed with AngularJS that can be included within an AngularJS + * application to provide support for CSS and JavaScript animation hooks. + * + * To make use of animations with AngularJS, the `angular-animate.js` JavaScript file must be included into your application + * and the `ngAnimate` module must be included as a dependency. + * + * <pre> + * angular.module('App', ['ngAnimate']); + * </pre> + * + * Then, to see animations in action, all that is required is to define the appropriate CSS classes + * or to register a JavaScript animation via the $animation service. The directives that support animation automatically are: + * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView`. Custom directives can take advantage of animation + * by using the `$animate` service. + * + * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: + * + * | Directive                                                 | Supported Animations                               | + * |========================================================== |====================================================| + * | {@link ng.directive:ngRepeat#animations ngRepeat}         | enter, leave and move                              | + * | {@link ngRoute.directive:ngView#animations ngView}        | enter and leave                                    | + * | {@link ng.directive:ngInclude#animations ngInclude}       | enter and leave                                    | + * | {@link ng.directive:ngSwitch#animations ngSwitch}         | enter and leave                                    | + * | {@link ng.directive:ngIf#animations ngIf}                 | enter and leave                                    | + * | {@link ng.directive:ngShow#animations ngShow & ngHide}    | show and hide                                      | + * | {@link ng.directive:ngShow#animations ngClass}            | add and remove                                     | + * + * You can find out more information about animations upon visiting each directive page. + * + * Below is an example of how to apply animations to a directive that supports animation hooks: + * + * <pre> + * <style type="text/css"> + * .slide.ng-enter > div, + * .slide.ng-leave > div { + *   -webkit-transition:0.5s linear all; + *   -moz-transition:0.5s linear all; + *   -o-transition:0.5s linear all; + *   transition:0.5s linear all; + * } + *  + * .slide > .ng-enter { }        /* starting animations for enter */ + * .slide > .ng-enter-active { } /* terminal animations for enter */ + * .slide > .ng-leave { }        /* starting animations for leave */ + * .slide > .ng-leave-active { } /* terminal animations for leave */ + * </style> + * + * <!-- + * the animate service will automatically add .ng-enter and .ng-leave to the element + * to trigger the CSS animations + * --> + * <ANY class="slide" ng-include="..."></ANY> + * </pre> + * + * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's + * animation has completed. + * + * <h2>CSS-defined Animations</h2> + * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes + * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported + * and can be used to play along with this naming structure. + * + * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: + * + * <pre> + * <style type="text/css"> + * /* + *  The animate class is apart of the element and the ng-enter class + *  is attached to the element once the enter animation event is triggered + * */ + * .reveal-animation.ng-enter { + *  -webkit-transition: 1s linear all; /* Safari/Chrome */ + *  -moz-transition: 1s linear all; /* Firefox */ + *  -o-transition: 1s linear all; /* Opera */ + *  transition: 1s linear all; /* IE10+ and Future Browsers */ + * + *  /* The animation preparation code */ + *  opacity: 0; + * } + * + * /* + *  Keep in mind that you want to combine both CSS + *  classes together to avoid any CSS-specificity + *  conflicts + * */ + * .reveal-animation.ng-enter.ng-enter-active { + *  /* The animation code itself */ + *  opacity: 1; + * } + * </style> + * + * <div class="view-container"> + *   <div ng-view class="reveal-animation"></div> + * </div> + * </pre> + * + * The following code below demonstrates how to perform animations using **CSS animations** with Angular: + * + * <pre> + * <style type="text/css"> + * .reveal-animation.ng-enter { + *   -webkit-animation: enter_sequence 1s linear; /* Safari/Chrome */ + *   -moz-animation: enter_sequence 1s linear; /* Firefox */ + *   -o-animation: enter_sequence 1s linear; /* Opera */ + *   animation: enter_sequence 1s linear; /* IE10+ and Future Browsers */ + * } + * @-webkit-keyframes enter_sequence { + *   from { opacity:0; } + *   to { opacity:1; } + * } + * @-moz-keyframes enter_sequence { + *   from { opacity:0; } + *   to { opacity:1; } + * } + * @-o-keyframes enter_sequence { + *   from { opacity:0; } + *   to { opacity:1; } + * } + * @keyframes enter_sequence { + *   from { opacity:0; } + *   to { opacity:1; } + * } + * </style> + * + * <div class="view-container"> + *   <div ng-view class="reveal-animation"></div> + * </div> + * </pre> + * + * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. + * + * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add + * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically + * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be + * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end + * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element + * has no CSS transition/animation classes applied to it. + * + * <h2>JavaScript-defined Animations</h2> + * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not + * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. + * + * <pre> + * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application. + * var ngModule = angular.module('YourApp', []); + * ngModule.animation('.my-crazy-animation', function() { + *   return { + *     enter: function(element, done) { + *       //run the animation + *       //!annotate Cancel Animation|This function (if provided) will perform the cancellation of the animation when another is triggered + *       return function(element, done) { + *         //cancel the animation + *       } + *     } + *     leave: function(element, done) { }, + *     move: function(element, done) { }, + *     show: function(element, done) { }, + *     hide: function(element, done) { }, + *     addClass: function(element, className, done) { }, + *     removeClass: function(element, className, done) { }, + *   } + * }); + * </pre> + * + * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run + * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits + * the element's CSS class attribute value and then run the matching animation event function (if found). + * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function + * be executed. It should be also noted that only simple or compound class selectors are allowed. + * + * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. + * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, + * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation + * or transition code that is defined via a stylesheet). + * + */ + +angular.module('ngAnimate', ['ng']) + +  /** +   * @ngdoc object +   * @name ngAnimate.$animateProvider +   * @description +   * +   * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside +   * of a module. When an animation is triggered, the $animate service will query the $animation function to find any +   * animations that match the provided name value. +   * +   * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application. +   * +   */ +  .config(['$provide', '$animateProvider', function($provide, $animateProvider) { +    var selectors = $animateProvider.$$selectors; + +    var NG_ANIMATE_STATE = '$$ngAnimateState'; +    var rootAnimateState = {running:true}; + +    $provide.decorator('$animate', ['$delegate', '$injector', '$window', '$sniffer', '$rootElement', +      function($delegate, $injector, $window, $sniffer, $rootElement) { +         +      var noop = angular.noop; +      var forEach = angular.forEach; + +      $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); + +       function lookup(name) { +        if (name) { +          var classes = name.substr(1).split('.'), +              classMap = {}; + +          for (var i = 0, ii = classes.length; i < ii; i++) { +            classMap[classes[i]] = true; +          } + +          var matches = []; +          for (var i = 0, ii = selectors.length; i < ii; i++) { +            var selectorFactory = selectors[i]; +            var found = true; +            for(var j = 0, jj = selectorFactory.selectors.length; j < jj; j++) { +              var klass = selectorFactory.selectors[j]; +              if(klass.length > 0) { +                found = found && classMap[klass]; +              } +            } +            if(found) { +              matches.push($injector.get(selectorFactory.name)); +            } +          } +          return matches; +        } +      }; + +      /** +       * @ngdoc object +       * @name ngAnimate.$animate +       * @requires $window, $sniffer, $rootElement +       * @function +       * +       * @description +       * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) +       * as well as during addClass and removeClass operations. When any of these operations are run, the $animate service +       * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) +       * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. +       * +       * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives +       * will work out of the box without any extra configuration. +       * +       * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application. +       * +       */ +      return { +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#enter +         * @methodOf ngAnimate.$animate +         * @function +         * +         * @description +         * Appends the element to the parent element that resides in the document and then runs the enter animation. Once +         * the animation is started, the following CSS classes will be present on the element for the duration of the animation: +         * <pre> +         * .ng-enter +         * .ng-enter-active +         * </pre> +         * +         * Once the animation is complete then the done callback, if provided, will be also fired. +         * +         * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation +         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation +         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        enter : function(element, parent, after, done) { +          $delegate.enter(element, parent, after); +          performAnimation('enter', 'ng-enter', element, parent, after, done); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#leave +         * @methodOf ngAnimate.$animate +         * @function +         * +         * @description +         * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once +         * the animation is started, the following CSS classes will be added for the duration of the animation: +         * <pre> +         * .ng-leave +         * .ng-leave-active +         * </pre> +         * +         * Once the animation is complete then the done callback, if provided, will be also fired. +         * +         * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        leave : function(element, done) { +          performAnimation('leave', 'ng-leave', element, null, null, function() { +            $delegate.leave(element, done); +          }); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#move +         * @methodOf ngAnimate.$animate +         * @function +         * +         * @description +         * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parent container or +         * add the element directly after the after element if present. Then the move animation will be run. Once +         * the animation is started, the following CSS classes will be added for the duration of the animation: +         * <pre> +         * .ng-move +         * .ng-move-active +         * </pre> +         * +         * Once the animation is complete then the done callback, if provided, will be also fired. +         * +         * @param {jQuery/jqLite element} element the element that will be the focus of the move animation +         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation +         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        move : function(element, parent, after, done) { +          $delegate.move(element, parent, after); +          performAnimation('move', 'ng-move', element, null, null, done); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#show +         * @methodOf ngAnimate.$animate +         * @function +         * +         * @description +         * Reveals the element by removing the `ng-hide` class thus performing an animation in the process. During +         * this animation the CSS classes present on the element will be: +         * +         * <pre> +         * .ng-hide //already on the element if hidden +         * .ng-hide-remove +         * .ng-hide-remove-active +         * </pre> +         * +         * Once the animation is complete then all three CSS classes will be removed from the element. +         * The done callback, if provided, will be also fired once the animation is complete. +         * +         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        show : function(element, done) { +          performAnimation('show', 'ng-hide-remove', element, null, null, function() { +            $delegate.show(element, done); +          }); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#hide +         * @methodOf ngAnimate.$animate +         * +         * @description +         * Sets the element to hidden by adding the `ng-hide` class it. However, before the class is applied +         * the following CSS classes will be added temporarily to trigger any animation code: +         * +         * <pre> +         * .ng-hide-add +         * .ng-hide-add-active +         * </pre> +         * +         * Once the animation is complete then both CSS classes will be removed and `ng-hide` will be added to the element. +         * The done callback, if provided, will be also fired once the animation is complete. +         * +         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        hide : function(element, done) { +          performAnimation('hide', 'ng-hide-add', element, null, null, function() { +            $delegate.hide(element, done); +          }); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#addClass +         * @methodOf ngAnimate.$animate +         * +         * @description +         * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. +         * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide +         * the animate service the setup and active CSS classes in order to trigger the animation. +         * +         * For example, upon execution of: +         * +         * <pre> +         * $animate.addClass(element, 'super'); +         * </pre> +         * +         * The generated CSS class values present on element will look like: +         * <pre> +         * .super-add +         * .super-add-active +         * </pre> +         * +         * And upon completion, the generated animation CSS classes will be removed from the element, but the className +         * value will be attached to the element. In this case, based on the previous example, the resulting CSS class for the element +         * will look like so: +         * +         * <pre> +         * .super +         * </pre> +         * +         * Once this is complete, then the done callback, if provided, will be fired. +         * +         * @param {jQuery/jqLite element} element the element that will be animated +         * @param {string} className the CSS class that will be animated and then attached to the element +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        addClass : function(element, className, done) { +          performAnimation('addClass', className, element, null, null, function() { +            $delegate.addClass(element, className, done); +          }); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#removeClass +         * @methodOf ngAnimate.$animate +         * +         * @description +         * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value +         * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in +         * order to provide the animate service the setup and active CSS classes in order to trigger the animation. +         * +         * For example, upon the execution of: +         * +         * <pre> +         * $animate.removeClass(element, 'super'); +         * </pre> +         * +         * The CSS class values present on element during the animation will look like: +         * +         * <pre> +         * .super //this was here from before +         * .super-remove +         * .super-remove-active +         * </pre> +         * +         * And upon completion, the generated animation CSS classes will be removed from the element as well as the +         * className value that was provided (in this case {@type super} will be removed). Once that is complete, then, if provided, +         * the done callback will be fired. +         * +         * @param {jQuery/jqLite element} element the element that will be animated +         * @param {string} className the CSS class that will be animated and then removed from the element +         * @param {function()=} done callback function that will be called once the animation is complete +        */ +        removeClass : function(element, className, done) { +          performAnimation('removeClass', className, element, null, null, function() { +            $delegate.removeClass(element, className, done); +          }); +        }, + +        /** +         * @ngdoc function +         * @name ngAnimate.$animate#enabled +         * @methodOf ngAnimate.$animate +         * @function +         * +         * @param {boolean=} If provided then set the animation on or off. +         * @return {boolean} Current animation state. +         * +         * @description +         * Globally enables/disables animations. +         * +        */ +        enabled : function(value) { +          if (arguments.length) { +            rootAnimateState.running = !value; +          } +          return !rootAnimateState.running; +        } +      }; + +      /* +        all animations call this shared animation triggering function internally. +        The event variable refers to the JavaScript animation event that will be triggered +        and the className value is the name of the animation that will be applied within the +        CSS code. Element, parent and after are provided DOM elements for the animation +        and the onComplete callback will be fired once the animation is fully complete. +      */ +      function performAnimation(event, className, element, parent, after, onComplete) { +        if(nothingToAnimate(className, element)) { +          (onComplete || noop)(); +        } else { +          var classes = ((element.attr('class') || '') + ' ' + className), +              animationLookup = (' ' + classes).replace(/\s+/g,'.'), +              animations = []; +          forEach(lookup(animationLookup), function(animation, index) { +            animations.push({ +              start : animation[event], +              done : false +            }); +          }); + +          if (!parent) { +            parent = after ? after.parent() : element.parent(); +          } +          var disabledAnimation = { running : true }; + +          //skip the animation if animations are disabled, a parent is already being animated +          //or the element is not currently attached to the document body. +          if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running) { +            //avoid calling done() since there is no need to remove any +            //data or className values since this happens earlier than that +            (onComplete || noop)(); +            return; +          } + +          var animationData = element.data(NG_ANIMATE_STATE) || {}; + +          //if an animation is currently running on the element then lets take the steps +          //to cancel that animation and fire any required callbacks +          if(animationData.running) { +            cancelAnimations(animationData.animations); +            animationData.done(); +          } + +          element.data(NG_ANIMATE_STATE, { +            running:true, +            animations:animations, +            done:done +          }); + +          if(event == 'addClass') { +            className = suffixClasses(className, '-add'); +          } else if(event == 'removeClass') { +            className = suffixClasses(className, '-remove'); +          } + +          element.addClass(className); + +          forEach(animations, function(animation, index) { +            var fn = function() { +              progress(index); +            }; + +            if(animation.start) { +              if(event == 'addClass' || event == 'removeClass') { +                animation.cancel = animation.start(element, className, fn); +              } else { +                animation.cancel = animation.start(element, fn); +              } +            } else { +              fn(); +            } +          }); +        } + +        function nothingToAnimate(className, element) { +          return !(className && className.length > 0 && element.length > 0); +        } + +        function cancelAnimations(animations) { +          forEach(animations, function(animation) { +            (animation.cancel || noop)(element); +          }); +        } + +        function suffixClasses(classes, suffix) { +          var className = ''; +          classes = angular.isArray(classes) ? classes : classes.split(/\s+/); +          forEach(classes, function(klass, i) { +            if(klass && klass.length > 0) { +              className += (i > 0 ? ' ' : '') + klass + suffix; +            } +          }); +          return className; +        } + +        function progress(index) { +          animations[index].done = true; +          for(var i=0;i<animations.length;i++) { +            if(!animations[i].done) return; +          } +          done(); +        }; + +        function done() { +          if(!done.hasBeenRun) { +            done.hasBeenRun = true; +            element.removeClass(className); +            element.removeData(NG_ANIMATE_STATE); +            (onComplete || noop)(); +          } +        } +      } +    }]); +  }]) + +  .animation('', ['$window','$sniffer', function($window, $sniffer) { +    return { +      enter : function(element, done) { +        return animate(element, 'ng-enter', done); +      }, +      leave : function(element, done) { +        return animate(element, 'ng-leave', done); +      }, +      move : function(element, done) { +        return animate(element, 'ng-move', done); +      }, +      show : function(element, done) { +        return animate(element, 'ng-hide-remove', done); +      }, +      hide : function(element, done) { +        return animate(element, 'ng-hide-add', done); +      }, +      addClass : function(element, className, done) { +        return animate(element, className, done); +      }, +      removeClass : function(element, className, done) { +        return animate(element, className, done); +      } +    }; + +    function animate(element, className, done) { +      if (!($sniffer.transitions || $sniffer.animations)) { +        done(); +      } else { +        var activeClassName = ''; +        $window.setTimeout(startAnimation, 1); + +        //this acts as the cancellation function in case +        //a new animation is triggered while another animation +        //is still going on (otherwise the active className +        //would still hang around until the timer is complete). +        return onComplete; +      } + +      function parseMaxTime(str) { +        var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : []; +        forEach(values, function(value) { +          total = Math.max(parseFloat(value) || 0, total); +        }); +        return total; +      } + +      function startAnimation() { +        var duration = 0; +        forEach(className.split(' '), function(klass, i) { +          activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; +        }); + +        element.addClass(activeClassName); + +        //one day all browsers will have these properties +        var w3cAnimationProp = 'animation'; +        var w3cTransitionProp = 'transition'; + +        //but some still use vendor-prefixed styles +        var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation'; +        var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition'; + +        var durationKey = 'Duration', +            delayKey = 'Delay', +            animationIterationCountKey = 'IterationCount'; + +        //we want all the styles defined before and after +        var ELEMENT_NODE = 1; +        forEach(element, function(element) { +          if (element.nodeType == ELEMENT_NODE) { +            var elementStyles = $window.getComputedStyle(element) || {}; + +            var transitionDelay     = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + delayKey]), +                                               parseMaxTime(elementStyles[vendorTransitionProp  + delayKey])); + +            var animationDelay      = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + delayKey]), +                                               parseMaxTime(elementStyles[vendorAnimationProp   + delayKey])); + +            var transitionDuration  = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + durationKey]), +                                               parseMaxTime(elementStyles[vendorTransitionProp  + durationKey])); + +            var animationDuration   = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + durationKey]), +                                               parseMaxTime(elementStyles[vendorAnimationProp   + durationKey])); + +            if(animationDuration > 0) { +              animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp   + animationIterationCountKey]) || 0, +                                           parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0, +                                           1); +            } + +            duration = Math.max(animationDelay  + animationDuration, +                                transitionDelay + transitionDuration, +                                duration); +          } +        }); + +        $window.setTimeout(onComplete, duration * 1000); +      } + +      function onComplete() { +        element.removeClass(activeClassName); +        done(); +      }; +    }; +  }]); diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index e05e7a28..bfb601fd 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -627,6 +627,43 @@ angular.mock.$LogProvider = function() {    angular.mock.TzDate.prototype = Date.prototype;  })(); +angular.mock.animate = angular.module('mock.animate', ['ng']) + +  .config(['$provide', function($provide) { + +    $provide.decorator('$animate', function($delegate) { +      var animate = { +        queue : [], +        enabled : $delegate.enabled, +        process : function(name) { +          var tick = animate.queue.shift(); +          expect(tick.method).toBe(name); +          tick.fn(); +          return tick; +        } +      }; + +      forEach(['enter','leave','move','show','hide','addClass','removeClass'], function(method) { +        animate[method] = function() { +          var params = arguments; +          animate.queue.push({ +            method : method, +            params : params, +            element : angular.isElement(params[0]) && params[0], +            parent  : angular.isElement(params[1]) && params[1], +            after   : angular.isElement(params[2]) && params[2], +            fn : function() { +              $delegate[method].apply($delegate, params); +            } +          }); +        }; +      }); + +      return animate; +    }); + +  }]); +  /**   * @ngdoc function   * @name angular.mock.createMockWindow diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js index 935ba05d..3074df49 100644 --- a/src/ngRoute/directive/ngView.js +++ b/src/ngRoute/directive/ngView.js @@ -14,9 +14,6 @@ ngRouteModule.directive('ngView', ngViewFactory);   * Every time the current route changes, the included view changes with it according to the   * configuration of the `$route` service.   * - * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter** - * and **leave** effects. - *   * @animations   * enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM)   * leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM @@ -35,8 +32,8 @@ ngRouteModule.directive('ngView', ngViewFactory);            <div              ng-view -            class="example-animate-container" -            ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div> +            class="example-$animate-container" +            ng-$animate="{enter: 'example-enter', leave: 'example-leave'}"></div>            <hr />            <pre>$location.path() = {{main.$location.path()}}</pre> @@ -71,12 +68,12 @@ ngRouteModule.directive('ngView', ngViewFactory);            transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;          } -        .example-animate-container { +        .example-$animate-container {            position:relative;            height:100px;          } -        .example-animate-container > * { +        .example-$animate-container > * {            display:block;            width:100%;            border-left:1px solid black; @@ -162,15 +159,14 @@ ngRouteModule.directive('ngView', ngViewFactory);   * @description   * Emitted every time the ngView content is reloaded.   */ -ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animator']; -function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,   $animator) { +ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; +function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,   $animate) {    return {      restrict: 'ECA',      terminal: true,      link: function(scope, element, attr) {        var lastScope, -          onloadExp = attr.onload || '', -          animate = $animator(scope, attr); +          onloadExp = attr.onload || '';        scope.$on('$routeChangeSuccess', update);        update(); @@ -184,7 +180,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,        }        function clearContent() { -        animate.leave(element.contents(), element); +        $animate.leave(element.contents());          destroyLastScope();        } @@ -195,7 +191,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,          if (template) {            clearContent();            var enterElements = jqLite('<div></div>').html(template).contents(); -          animate.enter(enterElements, element); +          $animate.enter(enterElements, element);            var link = $compile(enterElements),                current = $route.current, diff --git a/test/matchers.js b/test/matchers.js index 67efd3e7..ef89e3ee 100644 --- a/test/matchers.js +++ b/test/matchers.js @@ -30,11 +30,23 @@ beforeEach(function() {      return -1;    } +  function isNgElementHidden(element) { +    return angular.element(element).hasClass('ng-hide'); +  }; +    this.addMatchers({      toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),      toBeValid: cssMatcher('ng-valid', 'ng-invalid'),      toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),      toBePristine: cssMatcher('ng-pristine', 'ng-dirty'), +    toBeShown: function() { +      this.message = valueFn("Expected element to not have 'ng-hide' class"); +      return !isNgElementHidden(this.actual); +    }, +    toBeHidden: function() { +      this.message = valueFn("Expected element to have 'ng-hide' class"); +      return isNgElementHidden(this.actual); +    },      toEqual: function(expected) {        if (this.actual && this.actual.$$log) { diff --git a/test/ng/animateSpec.js b/test/ng/animateSpec.js new file mode 100644 index 00000000..7f440bb5 --- /dev/null +++ b/test/ng/animateSpec.js @@ -0,0 +1,53 @@ +describe("$animate", function() { + +  describe("without animation", function() { +    beforeEach(inject(function($compile, _$rootElement_, $rootScope) { +      element = $compile('<div></div>')($rootScope); +      $rootElement = _$rootElement_; +    })); + +    it("should add element at the start of enter animation", inject(function($animate, $compile, $rootScope) { +      var child = $compile('<div></div>')($rootScope); +      expect(element.contents().length).toBe(0); +      $animate.enter(child, element); +      expect(element.contents().length).toBe(1); +    })); + +    it("should remove the element at the end of leave animation", inject(function($animate, $compile, $rootScope) { +      var child = $compile('<div></div>')($rootScope); +      element.append(child); +      expect(element.contents().length).toBe(1); +      $animate.leave(child); +      expect(element.contents().length).toBe(0); +    })); + +    it("should reorder the move animation", inject(function($animate, $compile, $rootScope) { +      var child1 = $compile('<div>1</div>')($rootScope); +      var child2 = $compile('<div>2</div>')($rootScope); +      element.append(child1); +      element.append(child2); +      expect(element.text()).toBe('12'); +      $animate.move(child1, element, child2); +      expect(element.text()).toBe('21'); +    })); + +    it("should animate the show animation event", inject(function($animate) { +      element.addClass('ng-hide'); +      $animate.show(element); +      expect(element).toBeShown(); +    })); + +    it("should animate the hide animation event", inject(function($animate) { +      expect(element).toBeShown(); +      $animate.hide(element); +      expect(element).toBeHidden(); +    })); + +    it("should still perform DOM operations even if animations are disabled", inject(function($animate) { +      $animate.enabled(false); +      expect(element).toBeShown(); +      $animate.hide(element); +      expect(element).toBeHidden(); +    })); +  }); +}); diff --git a/test/ng/animationSpec.js b/test/ng/animationSpec.js deleted file mode 100644 index 86592643..00000000 --- a/test/ng/animationSpec.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -describe('$animation', function() { - -  it('should allow animation registration', function() { -    var noopCustom = function(){}; -    module(function($animationProvider) { -      $animationProvider.register('noop-custom', valueFn(noopCustom)); -    }); -    inject(function($animation) { -      expect($animation('noop-custom')).toBe(noopCustom); -    });  -  }); - -}); diff --git a/test/ng/animatorSpec.js b/test/ng/animatorSpec.js deleted file mode 100644 index 8fb9f05f..00000000 --- a/test/ng/animatorSpec.js +++ /dev/null @@ -1,773 +0,0 @@ -'use strict'; - -describe("$animator", function() { - -  var body, element, $rootElement; - -  function html(html) { -    body.append($rootElement); -    $rootElement.html(html); -    element = $rootElement.children().eq(0); -    return element; -  } - -  beforeEach(function() { -    // we need to run animation on attached elements; -    body = jqLite(document.body); -  }); - -  afterEach(function(){ -    dealoc(body); -  }); - -  describe("enable / disable", function() { - -    beforeEach(function() { -      module(function($animationProvider, $provide) { -        $provide.value('$window', angular.mock.createMockWindow()); -      }); -    }); - -    it("should disable and enable the animations", function() { -      var initialState = null; -      var animator; - -      angular.bootstrap(body, [function() { -        return function($animator) { -          animator = $animator; -          initialState = $animator.enabled(); -        } -      }]); - -      expect(initialState).toBe(false); - -      expect(animator.enabled()).toBe(true); - -      expect(animator.enabled(0)).toBe(false); -      expect(animator.enabled()).toBe(false); - -      expect(animator.enabled(1)).toBe(true); -      expect(animator.enabled()).toBe(true); -    }); - -  }); - -  describe("without animation", function() { -    var window, animator; - -    beforeEach(function() { -      module(function($animationProvider, $provide) { -        $provide.value('$window', window = angular.mock.createMockWindow()); -      }) -      inject(function($animator, $compile, $rootScope, _$rootElement_) { -        animator = $animator($rootScope, {}); -        element = $compile('<div></div>')($rootScope); -        $rootElement = _$rootElement_; -      }) -    }); - -    it("should add element at the start of enter animation", inject(function($animator, $compile, $rootScope) { -      var child = $compile('<div></div>')($rootScope); -      expect(element.contents().length).toBe(0); -      animator.enter(child, element); -      expect(element.contents().length).toBe(1); -    })); - -    it("should remove the element at the end of leave animation", inject(function($animator, $compile, $rootScope) { -      var child = $compile('<div></div>')($rootScope); -      element.append(child); -      expect(element.contents().length).toBe(1); -      animator.leave(child, element); -      expect(element.contents().length).toBe(0); -    })); - -    it("should reorder the move animation", inject(function($animator, $compile, $rootScope) { -      var child1 = $compile('<div>1</div>')($rootScope); -      var child2 = $compile('<div>2</div>')($rootScope); -      element.append(child1); -      element.append(child2); -      expect(element.text()).toBe('12'); -      animator.move(child1, element, child2); -      expect(element.text()).toBe('21'); -    })); - -    it("should animate the show animation event", inject(function() { -      element.css('display','none'); -      expect(element.css('display')).toBe('none'); -      animator.show(element); -      expect(element[0].style.display).toBe(''); -    })); - -    it("should animate the hide animation event", inject(function() { -      element.css('display','block'); -      expect(element.css('display')).toBe('block'); -      animator.hide(element); -      expect(element.css('display')).toBe('none'); -    })); - -    it("should still perform DOM operations even if animations are disabled", inject(function($animator) { -      $animator.enabled(false); -      element.css('display','block'); -      expect(element.css('display')).toBe('block'); -      animator.hide(element); -      expect(element.css('display')).toBe('none'); -    })); -  }); - -  describe("with polyfill", function() { - -    var child, after, window, animator; - -    beforeEach(function() { -      module(function($animationProvider, $provide) { -        $provide.value('$window', window = angular.mock.createMockWindow()); -        $animationProvider.register('custom', function() { -          return { -            start: function(element, done) { -              done(); -            } -          } -        }); -       $animationProvider.register('custom-delay', function() { -          return { -            start: function(element, done) { -              window.setTimeout(done, 2000); -            }, -            cancel : function(element) { -              element.addClass('animation-cancelled'); -            } -          } -        }); -       $animationProvider.register('setup-memo', function() { -          return { -            setup: function(element) { -              return "memento"; -            }, -            start: function(element, done, memento) { -              element.text(memento); -              done(); -            } -          } -        }); -      }) -      inject(function($animator, $compile, $rootScope, $rootElement) { -        element = $compile('<div></div>')($rootScope); -        child   = $compile('<div></div>')($rootScope); -        after   = $compile('<div></div>')($rootScope); -        $rootElement.append(element); -      }); -    }) - -    it("should animate the enter animation event", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{enter: \'custom\'}' -      }); - -      expect(element.contents().length).toBe(0); -      animator.enter(child, element); -      window.setTimeout.expect(1).process(); -    })); - -    it("should animate the leave animation event", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{leave: \'custom\'}' -      }); - -      element.append(child); -      expect(element.contents().length).toBe(1); -      animator.leave(child, element); -      window.setTimeout.expect(1).process(); -      expect(element.contents().length).toBe(0); -    })); - -    it("should animate the move animation event", inject(function($animator, $compile, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{move: \'custom\'}' -      }); -      $rootScope.$digest(); -      var child1 = $compile('<div>1</div>')($rootScope); -      var child2 = $compile('<div>2</div>')($rootScope); -      element.append(child1); -      element.append(child2); -      expect(element.text()).toBe('12'); -      animator.move(child1, element, child2); -      expect(element.text()).toBe('21'); -      window.setTimeout.expect(1).process(); -    })); - -    it("should animate the show animation event", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{show: \'custom\'}' -      }); -      $rootScope.$digest(); -      element.css('display','none'); -      expect(element.css('display')).toBe('none'); -      animator.show(element); -      expect(element[0].style.display).toBe(''); -      window.setTimeout.expect(1).process(); -      expect(element[0].style.display).toBe(''); -    })); - -    it("should animate the hide animation event", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{hide: \'custom\'}' -      }); -      $rootScope.$digest(); -      element.css('display','block'); -      expect(element.css('display')).toBe('block'); -      animator.hide(element); -      expect(element.css('display')).toBe('block'); -      window.setTimeout.expect(1).process(); -      expect(element.css('display')).toBe('none'); -    })); - -    it("should assign the ngAnimate string to all events if a string is given", -        inject(function($animator, $sniffer, $rootScope) { -      $animator.enabled(true); -      if (!$sniffer.transitions) return; -      animator = $animator($rootScope, { -        ngAnimate : '"custom"' -      }); - -      $rootScope.$digest(); - -      //enter -      animator.enter(child, element); -      expect(child.attr('class')).toContain('custom-enter'); -      window.setTimeout.expect(1).process(); -      expect(child.attr('class')).toContain('custom-enter-active'); -      window.setTimeout.expect(0).process(); - -      //leave -      element.append(after); -      animator.move(child, element, after); -      expect(child.attr('class')).toContain('custom-move'); -      window.setTimeout.expect(1).process(); -      expect(child.attr('class')).toContain('custom-move-active'); -      window.setTimeout.expect(0).process(); - -      //hide -      animator.hide(child); -      expect(child.attr('class')).toContain('custom-hide'); -      window.setTimeout.expect(1).process(); -      expect(child.attr('class')).toContain('custom-hide-active'); -      window.setTimeout.expect(0).process(); - -      //show -      animator.show(child); -      expect(child.attr('class')).toContain('custom-show'); -      window.setTimeout.expect(1).process(); -      expect(child.attr('class')).toContain('custom-show-active'); -      window.setTimeout.expect(0).process(); - -      //leave -      animator.leave(child); -      expect(child.attr('class')).toContain('custom-leave'); -      window.setTimeout.expect(1).process(); -      expect(child.attr('class')).toContain('custom-leave-active'); -      window.setTimeout.expect(0).process(); -    })); - -    it("should run polyfillSetup and return the memento", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{show: \'setup-memo\'}' -      }); -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); -      animator.show(element); -      window.setTimeout.expect(1).process(); -      expect(element.text()).toBe('memento'); -    })); - -    it("should not run if animations are disabled", inject(function($animator, $rootScope) { -      $animator.enabled(false); - -      animator = $animator($rootScope, { -        ngAnimate : '{show: \'setup-memo\'}' -      }); -      $rootScope.$digest(); - -      element.text('123'); -      expect(element.text()).toBe('123'); -      animator.show(element); -      expect(element.text()).toBe('123'); - -      $animator.enabled(true); - -      animator.show(element); -      window.setTimeout.expect(1).process(); -      expect(element.text()).toBe('memento'); -    })); - -    it("should only call done() once and right away if another animation takes place in between", -      inject(function($animator, $rootScope) { -      $animator.enabled(true); - -      animator = $animator($rootScope, { -        ngAnimate : '{hide: \'custom-delay\', leave: \'custom-delay\'}' -      }); - -      element.append(child); - -      child.css('display','block'); -      animator.hide(child); -      window.setTimeout.expect(1).process(); -      expect(child.css('display')).toBe('block'); - -      animator.leave(child); -      expect(child.css('display')).toBe('none'); //hides instantly - -      //lets change this to prove that done doesn't fire anymore for the previous hide() operation -      child.css('display','block');  - -      window.setTimeout.expect(2000).process(); -      expect(child.css('display')).toBe('block'); //doesn't run the done() method to hide it - -      expect(element.children().length).toBe(1); //still animating - -      window.setTimeout.expect(1).process(); -      window.setTimeout.expect(2000).process(); -      expect(element.children().length).toBe(0); -    })); - -    it("should call the cancel callback when another animation is called on the same element", -      inject(function($animator, $rootScope) { -      $animator.enabled(true); - -      animator = $animator($rootScope, { -        ngAnimate : '{hide: \'custom-delay\', show: \'custom-delay\'}' -      }); - -      child.css('display','none'); -      element.data('foo', 'bar'); -      animator.show(element); -      window.setTimeout.expect(1).process(); - -      animator.hide(element); - -      expect(element.hasClass('animation-cancelled')).toBe(true); -      expect(element.data('foo')).toEqual('bar'); -    })); - -    it("should NOT clobber all data on an element when animation is finished", -      inject(function($animator, $rootScope) { -      $animator.enabled(true); - -      animator = $animator($rootScope, { -        ngAnimate : '{hide: \'custom-delay\', show: \'custom-delay\'}' -      }); - -      child.css('display','none'); -      element.data('foo', 'bar'); - -      animator.show(element); -      window.setTimeout.expect(1).process(); - -      animator.hide(element); - -      expect(element.data('foo')).toEqual('bar'); -    })); - - -    it("should properly animate custom animation events", inject(function($animator, $rootScope) { -      $animator.enabled(true); -      animator = $animator($rootScope, { -        ngAnimate : '{custom: \'setup-memo\'}' -      }); - -      element.text('123'); -      animator.animate('custom',element); -      window.setTimeout.expect(1).process(); -      expect(element.text()).toBe('memento'); -    })); -  }); - -  describe("with CSS3", function() { -    var window, animator, prefix, vendorPrefix; - -    beforeEach(function() { -      module(function($animationProvider, $provide) { -        $provide.value('$window', window = angular.mock.createMockWindow()); -        return function($sniffer, _$rootElement_, $animator) { -          vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; -          $rootElement = _$rootElement_; -          $animator.enabled(true); -        }; -      }) -    }); - -    it("should properly animate custom animations for specific animation events", -      inject(function($animator, $rootScope, $compile, $sniffer) { - -      $animator.enabled(true); -      var element = $compile(html('<div></div>'))($rootScope); - -      animator = $animator($rootScope, { -        ngAnimate : '{custom: \'special\'}' -      }); - -      animator.animate('custom',element); -      if($sniffer.transitions) { -        expect(element.hasClass('special')).toBe(true); -        window.setTimeout.expect(1).process(); -        expect(element.hasClass('special-active')).toBe(true); -      } -      else { -        expect(window.setTimeout.queue.length).toBe(0); -      } -    })); - -    it("should not animate custom animations if not specifically defined", -      inject(function($animator, $rootScope, $compile) { - -      $animator.enabled(true); -      var element = $compile(html('<div></div>'))($rootScope); - -      animator = $animator($rootScope, { -        ngAnimate : '{custom: \'special\'}' -      }); - -      expect(window.setTimeout.queue.length).toBe(0); -      animator.animate('custom1',element); -      expect(element.hasClass('special')).toBe(false); -      expect(window.setTimeout.queue.length).toBe(0); -    })); - -    it("should properly animate custom animations for general animation events", -      inject(function($animator, $rootScope, $compile, $sniffer) { - -      $animator.enabled(true); -      var element = $compile(html('<div></div>'))($rootScope); - -      animator = $animator($rootScope, { -        ngAnimate : "'special'" -      }); - -      animator.animate('custom',element); -      if($sniffer.transitions) { -        expect(element.hasClass('special-custom')).toBe(true); -        window.setTimeout.expect(1).process(); -        expect(element.hasClass('special-custom-active')).toBe(true); -      } -      else { -        expect(window.setTimeout.queue.length).toBe(0); -      } -    })); - -    describe("Animations", function() { -      it("should properly detect and make use of CSS Animations", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        var style = 'animation: some_animation 4s linear 0s 1 alternate;' + -                    vendorPrefix + 'animation: some_animation 4s linear 0s 1 alternate;'; -        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        animator.show(element); -        if ($sniffer.animations) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(4000).process(); -        } -        else { -          expect(window.setTimeout.queue.length).toBe(0); -        } -        expect(element[0].style.display).toBe(''); -      })); - -      it("should properly detect and make use of CSS Animations with multiple iterations", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        var style = 'animation-duration: 2s;' +  -                    'animation-iteration-count: 3;' + -                    vendorPrefix + 'animation-duration: 2s;' +  -                    vendorPrefix + 'animation-iteration-count: 3;'; -        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        animator.show(element); -        if ($sniffer.animations) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(6000).process(); -        } -        else { -          expect(window.setTimeout.queue.length).toBe(0); -        } -        expect(element[0].style.display).toBe(''); -      })); - -      it("should fallback to the animation duration if an infinite iteration is provided", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        var style = 'animation-duration: 2s;' +  -                    'animation-iteration-count: infinite;' + -                    vendorPrefix + 'animation-duration: 2s;' +  -                    vendorPrefix + 'animation-iteration-count: infinite;'; -        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        animator.show(element); -        if ($sniffer.animations) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(2000).process(); -        } -        else { -          expect(window.setTimeout.queue.length).toBe(0); -        } -        expect(element[0].style.display).toBe(''); -      })); - -      it("should consider the animation delay is provided", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        var style = 'animation-duration: 2s;' +  -                    'animation-delay: 10s;' + -                    'animation-iteration-count: 5;' + -                    vendorPrefix + 'animation-duration: 2s;' +  -                    vendorPrefix + 'animation-delay: 10s;' + -                    vendorPrefix + 'animation-iteration-count: 5;'; -        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        animator.show(element); -        if ($sniffer.transitions) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(20000).process(); -        } -        else { -          expect(window.setTimeout.queue.length).toBe(0); -        } -        expect(element[0].style.display).toBe(''); -      })); - -      it("should skip animations if disabled and run when enabled", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        $animator.enabled(false); -        var style = 'animation: some_animation 2s linear 0s 1 alternate;' + -                    vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;' - -        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); -        animator.show(element); -        expect(element[0].style.display).toBe(''); -      })); - -      it("should finish the previous animation when a new animation is started", -        inject(function($animator, $rootScope, $compile, $sniffer) { -          var style = 'animation: some_animation 2s linear 0s 1 alternate;' + -                      vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;' - -          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -          var animator = $animator($rootScope, { -            ngAnimate : '{show: \'show\', hide: \'hide\'}' -          }); - -          animator.show(element); -          if($sniffer.animations) { -            window.setTimeout.expect(1).process(); -            expect(element.hasClass('show')).toBe(true); -            expect(element.hasClass('show-active')).toBe(true); -          } -          else { //animation is skipped -            expect(window.setTimeout.queue.length).toBe(0); -          } - -          animator.hide(element); -          if(!$sniffer.animations) { -            expect(window.setTimeout.queue.length).toBe(0); -          } -          expect(element.hasClass('show')).toBe(false); -          expect(element.hasClass('show-active')).toBe(false); -      })); -    }); - -    describe("Transitions", function() { -      it("should skip transitions if disabled and run when enabled", -          inject(function($animator, $rootScope, $compile, $sniffer) { -        $animator.enabled(false); -        element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope); -        var animator = $animator($rootScope, { -          ngAnimate : '{show: \'inline-show\'}' -        }); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); -        animator.show(element); -        expect(element[0].style.display).toBe(''); - -        $animator.enabled(true); - -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        animator.show(element); -        if ($sniffer.transitions) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(1000).process(); -        } -        else { -          expect(window.setTimeout.queue.length).toBe(0); -        } -        expect(element[0].style.display).toBe(''); -      })); - -      it("should skip animations if disabled and run when enabled picking the longest specified duration", -        inject(function($animator, $rootScope, $compile, $sniffer) { -          $animator.enabled(true); -          element = $compile(html('<div style="' + vendorPrefix + 'transition-duration: 1s, 2000ms, 1s; ' + vendorPrefix + 'transition-property: height, left, opacity">foo</div>'))($rootScope); -          var animator = $animator($rootScope, { -            ngAnimate : '{show: \'inline-show\'}' -          }); -          element.css('display','none'); -          animator.show(element); -          if ($sniffer.transitions) { -            window.setTimeout.expect(1).process(); -            window.setTimeout.expect(2000).process(); -          } -          else { -            expect(window.setTimeout.queue.length).toBe(0); -          } -          expect(element[0].style.display).toBe(''); -        })); - -      it("should skip animations if disabled and run when enabled picking the longest specified duration/delay combination", -        inject(function($animator, $rootScope, $compile, $sniffer) { -          $animator.enabled(false); -          element = $compile(html('<div style="' + vendorPrefix +  -            'transition-duration: 1s, 0s, 1s; ' + vendorPrefix + -            'transition-delay: 2s, 1000ms, 2s; ' + vendorPrefix +  -            'transition-property: height, left, opacity">foo</div>'))($rootScope); - -          var animator = $animator($rootScope, { -            ngAnimate : '{show: \'inline-show\'}' -          }); - -          element.css('display','none'); -          expect(element.css('display')).toBe('none'); -          animator.show(element); -          expect(element[0].style.display).toBe(''); - -          $animator.enabled(true); - -          element.css('display','none'); -          expect(element.css('display')).toBe('none'); - -          animator.show(element); -          if ($sniffer.transitions) { -            window.setTimeout.expect(1).process(); -            window.setTimeout.expect(3000).process(); -          } -          else { -            expect(window.setTimeout.queue.length).toBe(0); -          } -          expect(element[0].style.display).toBe(''); -      })); - -      it("should select the highest duration and delay", -        inject(function($animator, $rootScope, $compile, $sniffer) { -          var styles = 'transition:1s linear all 2s;' +  -                       vendorPrefix + 'transition:1s linear all 2s;' +  -                       'animation:my_ani 10s 1s;' +  -                       vendorPrefix + 'animation:my_ani 10s 1s;'; - -          element = $compile(html('<div style="' + styles + '">foo</div>'))($rootScope); - -          var animator = $animator($rootScope, { -            ngAnimate : '{show: \'inline-show\'}' -          }); - -          element.css('display','none'); -          expect(element.css('display')).toBe('none'); - -          animator.show(element); -          if ($sniffer.transitions) { -            window.setTimeout.expect(1).process(); -            window.setTimeout.expect(11000).process(); -          } -          else { -            expect(window.setTimeout.queue.length).toBe(0); -          } -          expect(element[0].style.display).toBe(''); -      })); - -      it("should finish the previous transition when a new animation is started", -        inject(function($animator, $rootScope, $compile, $sniffer) { -          var style = 'transition: 1s linear all;' + -                      vendorPrefix + 'transition: 1s linear all;' - -          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); -          var animator = $animator($rootScope, { -            ngAnimate : '{show: \'show\', hide: \'hide\'}' -          }); - -          animator.show(element); -          if($sniffer.transitions) { -            window.setTimeout.expect(1).process(); -            expect(element.hasClass('show')).toBe(true); -            expect(element.hasClass('show-active')).toBe(true); -          } -          else { //animation is skipped -            expect(window.setTimeout.queue.length).toBe(0); -          } - -          animator.hide(element); -          if(!$sniffer.transitions) { -            expect(window.setTimeout.queue.length).toBe(0); -          } -          expect(element.hasClass('show')).toBe(false); -          expect(element.hasClass('show-active')).toBe(false); -      })); -    }); -  }); - -  describe('anmation evaluation', function () { -    it('should re-evaluate the animation expression on each animation', inject(function($animator, $rootScope) { -      var parent = jqLite('<div><span></span></div>'); -      var element = parent.find('span'); - -      $rootScope.animationFn = function () { throw new Error('too early'); }; -      var animate = $animator($rootScope, { ngAnimate: 'animationFn()' }); -      var log = ''; - -      $rootScope.animationFn = function () { log = 'abc' }; -      animate.enter(element, parent); -      expect(log).toEqual('abc'); - -      $rootScope.animationFn = function () { log = 'xyz' }; -      animate.enter(element, parent); -      expect(log).toEqual('xyz'); -    })); -  }); - -  it("should throw an error when an invalid ng-animate syntax is provided", inject(function($animator, $rootScope) { -    expect(function() { -      var animate = $animator($rootScope, { ngAnimate: ':' }); -      animate.enter(); -    }).toThrow("[$parse:syntax] Syntax Error: Token ':' not a primary expression at column 1 of the expression [:] starting at [:]."); -  })); -}); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 1f5aae95..74090932 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3096,8 +3096,8 @@ describe('$compile', function() {            '</div>')($rootScope);        $rootScope.$digest();        var spans = element.find('span'); -      expect(spans.eq(0).css('display')).toBe('none'); -      expect(spans.eq(1).css('display')).toBe('none'); +      expect(spans.eq(0)).toBeHidden(); +      expect(spans.eq(1)).toBeHidden();      })); @@ -3216,10 +3216,10 @@ describe('$compile', function() {            '</div>')($rootScope);        $rootScope.$digest();        var spans = element.find('span'); -      expect(spans.eq(0).css('display')).toBe('none'); -      expect(spans.eq(1).css('display')).toBe('none'); -      expect(spans.eq(2).css('display')).toBe('none'); -      expect(spans.eq(3).css('display')).toBe('none'); +      expect(spans.eq(0)).toBeHidden(); +      expect(spans.eq(1)).toBeHidden(); +      expect(spans.eq(2)).toBeHidden(); +      expect(spans.eq(3)).toBeHidden();      }));    });  }); diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js index 0cca57d5..8f2cb793 100755 --- a/test/ng/directive/ngIfSpec.js +++ b/test/ng/directive/ngIfSpec.js @@ -75,8 +75,7 @@ describe('ngIf', function () {  }); -describe('ngIf ngAnimate', function () { -  var vendorPrefix, window; +describe('ngIf animations', function () {    var body, element, $rootElement;    function html(html) { @@ -85,6 +84,8 @@ describe('ngIf ngAnimate', function () {      return element;    } +  beforeEach(module('mock.animate')); +    beforeEach(module(function() {      // we need to run animation on attached elements;      return function(_$rootElement_) { @@ -99,97 +100,52 @@ describe('ngIf ngAnimate', function () {      dealoc(element);    }); -  beforeEach(module(function($animationProvider, $provide) { -    $provide.value('$window', window = angular.mock.createMockWindow()); -    return function($sniffer, $animator) { -      vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; -      $animator.enabled(true); +  beforeEach(module(function($animateProvider, $provide) { +    return function($animate) { +      $animate.enabled(true);      };    })); -  it('should fire off the enter animation + add and remove the css classes', -    inject(function($compile, $rootScope, $sniffer) { +  it('should fire off the enter animation', +    inject(function($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new(); -      var style = vendorPrefix + 'transition: 1s linear all';        element = $compile(html(          '<div>' + -          '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' + +          '<div ng-if="value"><div>Hi</div></div>' +          '</div>'        ))($scope);        $rootScope.$digest();        $scope.$apply('value = true'); +      item = $animate.process('enter').element; +      expect(item.text()).toBe('Hi');        expect(element.children().length).toBe(1); -      var first = element.children()[0]; - -      if ($sniffer.transitions) { -        expect(first.className).toContain('custom-enter'); -        window.setTimeout.expect(1).process(); -        expect(first.className).toContain('custom-enter-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(first.className).not.toContain('custom-enter'); -      expect(first.className).not.toContain('custom-enter-active');    })); -  it('should fire off the leave animation + add and remove the css classes', -    inject(function ($compile, $rootScope, $sniffer) { +  it('should fire off the leave animation', +    inject(function ($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new(); -      var style = vendorPrefix + 'transition: 1s linear all';        element = $compile(html(          '<div>' + -          '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' + +          '<div ng-if="value"><div>Hi</div></div>' +          '</div>'        ))($scope);        $scope.$apply('value = true'); -      expect(element.children().length).toBe(1); -      var first = element.children()[0]; - -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('enter').element; +      expect(item.text()).toBe('Hi');        $scope.$apply('value = false'); -      expect(element.children().length).toBe($sniffer.transitions ? 1 : 0); +      expect(element.children().length).toBe(1); -      if ($sniffer.transitions) { -        expect(first.className).toContain('custom-leave'); -        window.setTimeout.expect(1).process(); -        expect(first.className).toContain('custom-leave-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('leave').element; +      expect(item.text()).toBe('Hi');        expect(element.children().length).toBe(0);    })); -  it('should catch and use the correct duration for animation', -    inject(function ($compile, $rootScope, $sniffer) { -      var $scope = $rootScope.$new(); -      var style = vendorPrefix + 'transition: 0.5s linear all'; -      element = $compile(html( -        '<div>' + -          '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' + -        '</div>' -      ))($scope); -      $scope.$apply('value = true'); - -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(500).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } -  })); -  }); diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index 6cb78755..286ee5af 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -341,8 +341,7 @@ describe('ngInclude', function() {    });  }); -describe('ngInclude ngAnimate', function() { -  var vendorPrefix, window; +describe('ngInclude animations', function() {    var body, element, $rootElement;    function html(html) { @@ -351,11 +350,6 @@ describe('ngInclude ngAnimate', function() {      return element;    } -  function applyCSS(element, cssProp, cssValue) { -    element.css(cssProp, cssValue); -    element.css(vendorPrefix + cssProp, cssValue); -  } -    beforeEach(module(function() {      // we need to run animation on attached elements;      return function(_$rootElement_) { @@ -370,107 +364,51 @@ describe('ngInclude ngAnimate', function() {      dealoc(element);    }); -  beforeEach(module(function($animationProvider, $provide) { -    $provide.value('$window', window = angular.mock.createMockWindow()); -    return function($sniffer, $animator) { -      vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; -      $animator.enabled(true); -    }; -  })); +  beforeEach(module('mock.animate'));    afterEach(function(){      dealoc(element);    }); -  it('should fire off the enter animation + add and remove the css classes', -    inject(function($compile, $rootScope, $templateCache, $sniffer) { +  it('should fire off the enter animation', +    inject(function($compile, $rootScope, $templateCache, $animate) { +      var item;        $templateCache.put('enter', [200, '<div>data</div>', {}]);        $rootScope.tpl = 'enter';        element = $compile(html(          '<div ' + -          'ng-include="tpl" ' + -          'ng-animate="{enter: \'custom-enter\'}">' + +          'ng-include="tpl">' +          '</div>'        ))($rootScope);        $rootScope.$digest(); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var child = jqLite(element.children()[0]); -      applyCSS(child, 'transition', '1s linear all'); - -      if ($sniffer.transitions) { -        expect(child.attr('class')).toContain('custom-enter'); -        window.setTimeout.expect(1).process(); - -        expect(child.attr('class')).toContain('custom-enter-active'); -        window.setTimeout.expect(1000).process(); -      } else { -       expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(child.attr('class')).not.toContain('custom-enter'); -      expect(child.attr('class')).not.toContain('custom-enter-active'); +      item = $animate.process('leave').element; +      item = $animate.process('enter').element; +      expect(item.text()).toBe('data');    })); -  it('should fire off the leave animation + add and remove the css classes', -    inject(function($compile, $rootScope, $templateCache, $sniffer) { +  it('should fire off the leave animation', +    inject(function($compile, $rootScope, $templateCache, $animate) { +      var item;        $templateCache.put('enter', [200, '<div>data</div>', {}]);        $rootScope.tpl = 'enter';        element = $compile(html(          '<div ' + -          'ng-include="tpl" ' + -          'ng-animate="{leave: \'custom-leave\'}">' + +          'ng-include="tpl">' +          '</div>'        ))($rootScope);        $rootScope.$digest(); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var child = jqLite(element.children()[0]); -      applyCSS(child, 'transition', '1s linear all'); +      item = $animate.process('leave').element; +      item = $animate.process('enter').element; +      expect(item.text()).toBe('data');        $rootScope.tpl = '';        $rootScope.$digest(); -      if ($sniffer.transitions) { -        expect(child.attr('class')).toContain('custom-leave'); -        window.setTimeout.expect(1).process(); - -        expect(child.attr('class')).toContain('custom-leave-active'); -        window.setTimeout.expect(1000).process(); -      } else { -       expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(child.attr('class')).not.toContain('custom-leave'); -      expect(child.attr('class')).not.toContain('custom-leave-active'); -  })); - -  it('should catch and use the correct duration for animation', -    inject(function($compile, $rootScope, $templateCache, $sniffer) { -      $templateCache.put('enter', [200, '<div>data</div>', {}]); -      $rootScope.tpl = 'enter'; -      element = $compile(html( -        '<div ' + -          'ng-include="tpl" ' + -          'ng-animate="{enter: \'custom-enter\'}">' + -        '</div>' -      ))($rootScope); -      $rootScope.$digest(); - -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var child = jqLite(element.children()[0]); -      applyCSS(child, 'transition', '0.5s linear all'); - -      $rootScope.tpl = 'enter'; -      $rootScope.$digest(); - -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(500).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('leave').element; +      expect(item.text()).toBe('data');    }));  }); diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 26562f4e..a85fd5ab 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -822,10 +822,30 @@ describe('ngRepeat', function() {        expect(newLis[2]).toEqual(lis[1]);      });    }); + +  it('should grow multi-node repeater', inject(function($compile, $rootScope) { +    $rootScope.show = false; +    $rootScope.books = [ +      {title:'T1', description: 'D1'}, +      {title:'T2', description: 'D2'} +    ]; +    element = $compile( +        '<div>' + +            '<dt ng-repeat-start="book in books">{{book.title}}:</dt>' + +            '<dd ng-repeat-end>{{book.description}};</dd>' + +        '</div>')($rootScope); + +    $rootScope.$digest(); +    expect(element.text()).toEqual('T1:D1;T2:D2;'); +    $rootScope.books.push({title:'T3', description: 'D3'}); +    $rootScope.$digest(); +    expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;'); +  })); + +  }); -describe('ngRepeat ngAnimate', function() { -  var vendorPrefix, window; +describe('ngRepeat animations', function() {    var body, element, $rootElement;    function html(html) { @@ -834,10 +854,7 @@ describe('ngRepeat ngAnimate', function() {      return element;    } -  function applyCSS(element, cssProp, cssValue) { -    element.css(cssProp, cssValue); -    element.css(vendorPrefix + cssProp, cssValue); -  } +  beforeEach(module('mock.animate'));    beforeEach(module(function() {      // we need to run animation on attached elements; @@ -853,21 +870,14 @@ describe('ngRepeat ngAnimate', function() {      dealoc(element);    }); -  beforeEach(module(function($animationProvider, $provide) { -    $provide.value('$window', window = angular.mock.createMockWindow()); -    return function($sniffer, $animator) { -      vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; -      $animator.enabled(true); -    }; -  })); +  it('should fire off the enter animation', +    inject(function($compile, $rootScope, $animate) { -  it('should fire off the enter animation + add and remove the css classes', -    inject(function($compile, $rootScope, $sniffer) { +    var item;      element = $compile(html(        '<div><div ' + -        'ng-repeat="item in items" ' + -        'ng-animate="{enter: \'custom-enter\'}">' + +        'ng-repeat="item in items">' +          '{{ item }}' +        '</div></div>'      ))($rootScope); @@ -877,40 +887,24 @@ describe('ngRepeat ngAnimate', function() {      $rootScope.items = ['1','2','3'];      $rootScope.$digest(); -    //if we add the custom css stuff here then it will get picked up before the animation takes place -    var kids = element.children(); -    for(var i=0;i<kids.length;i++) { -      kids[i] = jqLite(kids[i]); -      applyCSS(kids[i], 'transition', '1s linear all'); -    } - -    if ($sniffer.transitions) { -      angular.forEach(kids, function(kid) { -        expect(kid.attr('class')).toContain('custom-enter'); -        window.setTimeout.expect(1).process(); -      }); +    item = $animate.process('enter').element; +    expect(item.text()).toBe('1'); -      angular.forEach(kids, function(kid) { -        expect(kid.attr('class')).toContain('custom-enter-active'); -        window.setTimeout.expect(1000).process(); -      }); -    } else { -      expect(window.setTimeout.queue).toEqual([]); -    } +    item = $animate.process('enter').element; +    expect(item.text()).toBe('2'); -    angular.forEach(kids, function(kid) { -      expect(kid.attr('class')).not.toContain('custom-enter'); -      expect(kid.attr('class')).not.toContain('custom-enter-active'); -    }); +    item = $animate.process('enter').element; +    expect(item.text()).toBe('3');    })); -  it('should fire off the leave animation + add and remove the css classes', -    inject(function($compile, $rootScope, $sniffer) { +  it('should fire off the leave animation', +    inject(function($compile, $rootScope, $animate) { + +    var item;      element = $compile(html(        '<div><div ' + -        'ng-repeat="item in items" ' + -        'ng-animate="{leave: \'custom-leave\'}">' + +        'ng-repeat="item in items">' +          '{{ item }}' +        '</div></div>'      ))($rootScope); @@ -918,36 +912,30 @@ describe('ngRepeat ngAnimate', function() {      $rootScope.items = ['1','2','3'];      $rootScope.$digest(); -    //if we add the custom css stuff here then it will get picked up before the animation takes place -    var kids = element.children(); -    for(var i=0;i<kids.length;i++) { -      kids[i] = jqLite(kids[i]); -      applyCSS(kids[i], 'transition', '1s linear all'); -    } +    item = $animate.process('enter').element; +    expect(item.text()).toBe('1'); + +    item = $animate.process('enter').element; +    expect(item.text()).toBe('2'); + +    item = $animate.process('enter').element; +    expect(item.text()).toBe('3');      $rootScope.items = ['1','3'];      $rootScope.$digest(); -    //the last element gets pushed down when it animates -    var kid = jqLite(element.children()[1]); -    if ($sniffer.transitions) { -      expect(kid.attr('class')).toContain('custom-leave'); -      window.setTimeout.expect(1).process(); -      expect(kid.attr('class')).toContain('custom-leave-active'); -      window.setTimeout.expect(1000).process(); -    } else { -      expect(window.setTimeout.queue).toEqual([]); -    } - -    expect(kid.attr('class')).not.toContain('custom-leave'); -    expect(kid.attr('class')).not.toContain('custom-leave-active'); +    item = $animate.process('leave').element; +    expect(item.text()).toBe('2');    })); -  it('should fire off the move animation + add and remove the css classes', -    inject(function($compile, $rootScope, $sniffer) { +  it('should fire off the move animation', +    inject(function($compile, $rootScope, $animate) { + +      var item; +        element = $compile(html(          '<div>' + -          '<div ng-repeat="item in items" ng-animate="{move: \'custom-move\'}">' + +          '<div ng-repeat="item in items">' +              '{{ item }}' +            '</div>' +          '</div>' @@ -956,97 +944,23 @@ describe('ngRepeat ngAnimate', function() {        $rootScope.items = ['1','2','3'];        $rootScope.$digest(); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var kids = element.children(); -      for(var i=0;i<kids.length;i++) { -        kids[i] = jqLite(kids[i]); -        applyCSS(kids[i], 'transition', '1s linear all'); -      } +      item = $animate.process('enter').element; +      expect(item.text()).toBe('1'); -      $rootScope.items = ['2','3','1']; -      $rootScope.$digest(); - -      //the last element gets pushed down when it animates -      kids  = element.children(); -      var first = jqLite(kids[0]); -      var left  = jqLite(kids[1]); -      var right = jqLite(kids[2]); - -      if ($sniffer.transitions) { -        expect(first.attr('class')).toContain('custom-move'); -        window.setTimeout.expect(1).process(); -        expect(left.attr('class')).toContain('custom-move'); -        window.setTimeout.expect(1).process(); - -        expect(first.attr('class')).toContain('custom-move-active'); -        window.setTimeout.expect(1000).process(); -        expect(left.attr('class')).toContain('custom-move-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(first.attr('class')).not.toContain('custom-move'); -      expect(first.attr('class')).not.toContain('custom-move-active'); -      expect(left.attr('class')).not.toContain('custom-move'); -      expect(left.attr('class')).not.toContain('custom-move-active'); -      expect(right.attr('class')).not.toContain('custom-move'); -      expect(right.attr('class')).not.toContain('custom-move-active'); -  })); +      item = $animate.process('enter').element; +      expect(item.text()).toBe('2'); -  it('should catch and use the correct duration for animation', -    inject(function($compile, $rootScope, $sniffer) { +      item = $animate.process('enter').element; +      expect(item.text()).toBe('3'); -      element = $compile(html( -        '<div><div ' + -          'ng-repeat="item in items" ' + -          'ng-animate="{enter: \'custom-enter\'}">' + -          '{{ item }}' + -        '</div></div>' -      ))($rootScope); - -      $rootScope.$digest(); // re-enable the animations; - -      $rootScope.items = ['a','b']; +      $rootScope.items = ['2','3','1'];        $rootScope.$digest(); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var kids = element.children(); -      var first = jqLite(kids[0]); -      var second = jqLite(kids[1]); -      var cssProp = 'transition'; -      var cssValue = '0.5s linear all'; -      applyCSS(first, cssProp, cssValue); -      applyCSS(second, cssProp, cssValue); - -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(500).process(); -        window.setTimeout.expect(500).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } -  })); +      item = $animate.process('move').element; +      expect(item.text()).toBe('2'); -  it('should grow multi-node repeater', inject(function($compile, $rootScope) { -    $rootScope.show = false; -    $rootScope.books = [ -      {title:'T1', description: 'D1'}, -      {title:'T2', description: 'D2'} -    ]; -    element = $compile( -        '<div>' + -            '<dt ng-repeat-start="book in books">{{book.title}}:</dt>' + -            '<dd ng-repeat-end>{{book.description}};</dd>' + -        '</div>')($rootScope); - -    $rootScope.$digest(); -    expect(element.text()).toEqual('T1:D1;T2:D2;'); -    $rootScope.books.push({title:'T3', description: 'D3'}); -    $rootScope.$digest(); -    expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;'); +      item = $animate.process('move').element; +      expect(item.text()).toBe('1');    })); -  }); diff --git a/test/ng/directive/ngShowHideSpec.js b/test/ng/directive/ngShowHideSpec.js index 9b95440d..f8193a12 100644 --- a/test/ng/directive/ngShowHideSpec.js +++ b/test/ng/directive/ngShowHideSpec.js @@ -13,20 +13,20 @@ describe('ngShow / ngHide', function() {        element = jqLite('<div ng-show="exp"></div>');        element = $compile(element)($rootScope);        $rootScope.$digest(); -      expect(isCssVisible(element)).toEqual(false); +      expect(element).toBeHidden();        $rootScope.exp = true;        $rootScope.$digest(); -      expect(isCssVisible(element)).toEqual(true); +      expect(element).toBeShown();      }));      it('should make hidden element visible', inject(function($rootScope, $compile) { -      element = jqLite('<div style="display: none" ng-show="exp"></div>'); +      element = jqLite('<div class="ng-hide" ng-show="exp"></div>');        element = $compile(element)($rootScope); -      expect(isCssVisible(element)).toBe(false); +      expect(element).toBeHidden();        $rootScope.exp = true;        $rootScope.$digest(); -      expect(isCssVisible(element)).toBe(true); +      expect(element).toBeShown();      }));    }); @@ -34,17 +34,15 @@ describe('ngShow / ngHide', function() {      it('should hide an element', inject(function($rootScope, $compile) {        element = jqLite('<div ng-hide="exp"></div>');        element = $compile(element)($rootScope); -      expect(isCssVisible(element)).toBe(true); +      expect(element).toBeShown();        $rootScope.exp = true;        $rootScope.$digest(); -      expect(isCssVisible(element)).toBe(false); +      expect(element).toBeHidden();      }));    });  }); -describe('ngShow / ngHide - ngAnimate', function() { -  var window; -  var vendorPrefix; +describe('ngShow / ngHide animations', function() {    var body, element, $rootElement;    function html(html) { @@ -65,152 +63,57 @@ describe('ngShow / ngHide - ngAnimate', function() {      body.removeAttr('ng-animation-running');    }); -  beforeEach(module(function($animationProvider, $provide) { -    $provide.value('$window', window = angular.mock.createMockWindow()); -    return function($sniffer, _$rootElement_, $animator) { -      vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; +  beforeEach(module('mock.animate')); + +  beforeEach(module(function($animateProvider, $provide) { +    return function(_$rootElement_) {        $rootElement = _$rootElement_; -      $animator.enabled(true);      };    }));    describe('ngShow', function() { -    it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) { +    it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new();        $scope.on = true;        element = $compile(html( -        '<div ' + -          'style="'+vendorPrefix+'transition: 1s linear all"' + -          'ng-show="on" ' + -          'ng-animate="{show: \'custom-show\', hide: \'custom-hide\', animateFirst: true}">' + -        '</div>' +        '<div ng-show="on">data</div>'        ))($scope);        $scope.$digest(); -      if ($sniffer.transitions) { -        expect(element.attr('class')).toContain('custom-show'); -        window.setTimeout.expect(1).process(); - -        expect(element.attr('class')).toContain('custom-show-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(element.attr('class')).not.toContain('custom-show-active'); -      expect(element.attr('class')).not.toContain('custom-show'); +      item = $animate.process('show').element; +      expect(item.text()).toBe('data'); +      expect(item).toBeShown();        $scope.on = false;        $scope.$digest(); -      if ($sniffer.transitions) { -        expect(element.attr('class')).toContain('custom-hide'); -        window.setTimeout.expect(1).process(); -        expect(element.attr('class')).toContain('custom-hide-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(element.attr('class')).not.toContain('custom-hide-active'); -      expect(element.attr('class')).not.toContain('custom-hide'); -    })); -    it('should skip animation if parent animation running', function() { -      var fired = false; -      inject(function($animator, $compile, $rootScope, $sniffer) { -        $animator.enabled(true); -        $rootScope.$digest(); -        $rootScope.val = true; -        var element = $compile(html('<div ng-show="val" ng-animate="\'animation\'">123</div>'))($rootScope); -        $rootElement.controller('ngAnimate').running = true; -        element.css('display','none'); -        expect(element.css('display')).toBe('none'); - -        $rootScope.$digest(); -        expect(element[0].style.display).toBe(''); -        expect(fired).toBe(false); - -        $rootElement.controller('ngAnimate').running = false; -        $rootScope.val = false; -        $rootScope.$digest(); -        if ($sniffer.transitions) { -          window.setTimeout.expect(1).process(); -          window.setTimeout.expect(0).process(); -        } else { -          expect(window.setTimeout.queue).toEqual([]); -        } -        expect(element[0].style.display).toBe('none'); -      }); -    }); +      item = $animate.process('hide').element; +      expect(item.text()).toBe('data'); +      expect(item).toBeHidden(); +    }));    });    describe('ngHide', function() { -    it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) { +    it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new();        $scope.off = true;        element = $compile(html( -          '<div ' + -              'style="'+vendorPrefix+'transition: 1s linear all"' + -              'ng-hide="off" ' + -              'ng-animate="{show: \'custom-show\', hide: \'custom-hide\', animateFirst: true}">' + -          '</div>' +          '<div ng-hide="off">datum</div>'        ))($scope);        $scope.$digest(); -      if ($sniffer.transitions) { -        expect(element.attr('class')).toContain('custom-hide'); -        window.setTimeout.expect(1).process(); - -        expect(element.attr('class')).toContain('custom-hide-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(element.attr('class')).not.toContain('custom-hide-active'); -      expect(element.attr('class')).not.toContain('custom-hide'); +      item = $animate.process('hide').element; +      expect(item.text()).toBe('datum'); +      expect(item).toBeHidden();        $scope.off = false;        $scope.$digest(); -      if ($sniffer.transitions) { -        expect(element.attr('class')).toContain('custom-show'); -        window.setTimeout.expect(1).process(); -        expect(element.attr('class')).toContain('custom-show-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(element.attr('class')).not.toContain('custom-show-active'); -      expect(element.attr('class')).not.toContain('custom-show'); +      item = $animate.process('show').element; +      expect(item.text()).toBe('datum'); +      expect(item).toBeShown();      })); - -    it('should disable animation when parent animation is running', function() { -      var fired = false; -      module(function($animationProvider) { -        $animationProvider.register('destructive-animation', function() { -          return { -            setup : function() {}, -            start : function(element, done) { -              fired = true; -            } -          }; -        }); -      }); -      inject(function($compile, $rootScope) { -        $rootScope.val = false; -        var element = $compile(html('<div ng-hide="val" ng-animate="{ hide:\'destructive-animation\' }">123</div>'))($rootScope); -        $rootElement.controller('ngAnimate').running = true; -        element.css('display','block'); -        expect(element.css('display')).toBe('block'); - -        $rootScope.val = true; -        $rootScope.$digest(); - -        expect(element.css('display')).toBe('none'); -        expect(fired).toBe(false); -      }); -    });    });  }); diff --git a/test/ng/directive/ngSwitchSpec.js b/test/ng/directive/ngSwitchSpec.js index ab231ec2..8750b187 100644 --- a/test/ng/directive/ngSwitchSpec.js +++ b/test/ng/directive/ngSwitchSpec.js @@ -214,8 +214,7 @@ describe('ngSwitch', function() {    }));  }); -describe('ngSwitch ngAnimate', function() { -  var vendorPrefix, window; +describe('ngSwitch animations', function() {    var body, element, $rootElement;    function html(html) { @@ -224,6 +223,8 @@ describe('ngSwitch ngAnimate', function() {      return element;    } +  beforeEach(module('mock.animate')); +    beforeEach(module(function() {      // we need to run animation on attached elements;      return function(_$rootElement_) { @@ -238,23 +239,15 @@ describe('ngSwitch ngAnimate', function() {      dealoc(element);    }); -  beforeEach(module(function($animationProvider, $provide) { -    $provide.value('$window', window = angular.mock.createMockWindow()); -    return function($sniffer, $animator) { -      vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; -      $animator.enabled(true); -    }; -  })); - -  it('should fire off the enter animation + set and remove the classes', -    inject(function($compile, $rootScope, $sniffer) { +  it('should fire off the enter animation', +    inject(function($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new(); -      var style = vendorPrefix + 'transition: 1s linear all';        element = $compile(html( -        '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' + -          '<div ng-switch-when="one" style="' + style + '">one</div>' + -          '<div ng-switch-when="two" style="' + style + '">two</div>' + -          '<div ng-switch-when="three" style="' + style + '">three</div>' + +        '<div ng-switch on="val">' + +          '<div ng-switch-when="one">one</div>' + +          '<div ng-switch-when="two">two</div>' + +          '<div ng-switch-when="three">three</div>' +          '</div>'        ))($scope); @@ -262,33 +255,20 @@ describe('ngSwitch ngAnimate', function() {        $scope.val = 'one';        $scope.$digest(); -      expect(element.children().length).toBe(1); -      var first = element.children()[0]; - -      if ($sniffer.transitions) { -        expect(first.className).toContain('cool-enter'); -        window.setTimeout.expect(1).process(); - -        expect(first.className).toContain('cool-enter-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(first.className).not.toContain('cool-enter'); -      expect(first.className).not.toContain('cool-enter-active'); +      item = $animate.process('enter').element; +      expect(item.text()).toBe('one');    })); -  it('should fire off the leave animation + set and remove the classes', -    inject(function($compile, $rootScope, $sniffer) { +  it('should fire off the leave animation', +    inject(function($compile, $rootScope, $animate) { +      var item;        var $scope = $rootScope.$new(); -      var style = vendorPrefix + 'transition: 1s linear all';        element = $compile(html( -        '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' + -          '<div ng-switch-when="one" style="' + style + '">one</div>' + -          '<div ng-switch-when="two" style="' + style + '">two</div>' + -          '<div ng-switch-when="three" style="' + style + '">three</div>' + +        '<div ng-switch on="val">' + +          '<div ng-switch-when="one">one</div>' + +          '<div ng-switch-when="two">two</div>' + +          '<div ng-switch-when="three">three</div>' +          '</div>'        ))($scope); @@ -296,59 +276,17 @@ describe('ngSwitch ngAnimate', function() {        $scope.val = 'two';        $scope.$digest(); -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('enter').element; +      expect(item.text()).toBe('two');        $scope.val = 'three';        $scope.$digest(); -      expect(element.children().length).toBe($sniffer.transitions ? 2 : 1); -      var first = element.children()[0]; - - -      if ($sniffer.transitions) { -        expect(first.className).toContain('cool-leave'); -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(1).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('leave').element; +      expect(item.text()).toBe('two'); - -      if ($sniffer.transitions) { -        expect(first.className).toContain('cool-leave-active'); -        window.setTimeout.expect(1000).process(); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } - -      expect(first.className).not.toContain('cool-leave'); -      expect(first.className).not.toContain('cool-leave-active'); -  })); - -  it('should catch and use the correct duration for animation', -    inject(function($compile, $rootScope, $sniffer) { -      element = $compile(html( -        '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' + -          '<div ng-switch-when="one" style="' + vendorPrefix + 'transition: 0.5s linear all">one</div>' + -        '</div>' -      ))($rootScope); - -      $rootScope.$digest(); // re-enable the animations; -      $rootScope.val = 'one'; -      $rootScope.$digest(); - -      if ($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect(500).process(); -      } else { -       expect(window.setTimeout.queue).toEqual([]); -      } +      item = $animate.process('enter').element; +      expect(item.text()).toBe('three');    }));  }); diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js new file mode 100644 index 00000000..52bf5e0b --- /dev/null +++ b/test/ngAnimate/animateSpec.js @@ -0,0 +1,1524 @@ +'use strict'; + +describe("ngAnimate", function() { + +  beforeEach(module('ngAnimate')); + +  describe("$animate", function() { + +    var body, element, $rootElement; + +    function html(html) { +      body.append($rootElement); +      $rootElement.html(html); +      element = $rootElement.children().eq(0); +      return element; +    } + +    beforeEach(module(function() { +      // we need to run animation on attached elements; +      body = jqLite(document.body); +      return function($animate) { +        $animate.enabled(true); +      }; +    })); + +    afterEach(function(){ +      dealoc(body); +    }); + +    describe("enable / disable", function() { + +      beforeEach(function() { +        module(function($animateProvider, $provide) { +          $provide.value('$window', angular.mock.createMockWindow()); +        }); +      }); + +      it("should disable and enable the animations", function() { +        var $animate, initialState = null; + +        angular.bootstrap(body, ['ngAnimate',function() { +          return function(_$animate_) { +            $animate = _$animate_; +            initialState = $animate.enabled(); +          } +        }]); + +        expect(initialState).toBe(false); + +        expect($animate.enabled()).toBe(true); + +        expect($animate.enabled(0)).toBe(false); +        expect($animate.enabled()).toBe(false); + +        expect($animate.enabled(1)).toBe(true); +        expect($animate.enabled()).toBe(true); +      }); + +    }); + +    describe("with polyfill", function() { + +      var child, after, window; + +      beforeEach(function() { +        module(function($animateProvider, $provide) { +          $provide.value('$window', window = angular.mock.createMockWindow()); +          $animateProvider.register('.custom', function() { +            return { +              start: function(element, done) { +                done(); +              } +            } +          }); +         $animateProvider.register('.custom-delay', function() { +            function animate(element, done) { +              done = arguments.length == 3 ? arguments[2] : done; +              window.setTimeout(done, 2000); +              return function() { +                element.addClass('animation-cancelled'); +              } +            } +            return { +              show : animate, +              hide : animate, +              leave : animate, +              addClass : animate, +              removeClass : animate +            } +          }); +         $animateProvider.register('.custom-long-delay', function() { +            function animate(element, done) { +              done = arguments.length == 3 ? arguments[2] : done; +              window.setTimeout(done, 20000); +              return function() { +                element.addClass('animation-cancelled'); +              } +            } +            return { +              show : animate, +              hide : animate, +              leave : animate, +              addClass : animate, +              removeClass : animate +            } +          }); +         $animateProvider.register('.setup-memo', function() { +            return { +              show: function(element, done) { +                element.text('memento'); +                done(); +              } +            } +          }); +          return function($animate, $compile, $rootScope, $rootElement) { +            element = $compile('<div></div>')($rootScope); +            child   = $compile('<div></div>')($rootScope); +            after   = $compile('<div></div>')($rootScope); +            $rootElement.append(element); +          }; +        }); +      }) + +      it("should animate the enter animation event", inject(function($animate, $rootScope, $sniffer) { +        expect(element.contents().length).toBe(0); +        $animate.enter(child, element); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        expect(element.contents().length).toBe(1); +      })); + +      it("should animate the leave animation event", inject(function($animate, $rootScope, $sniffer) { +        element.append(child); +        expect(element.contents().length).toBe(1); +        $animate.leave(child); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        expect(element.contents().length).toBe(0); +      })); + +      it("should animate the move animation event", inject(function($animate, $compile, $rootScope, $sniffer) { +        $rootScope.$digest(); +        var child1 = $compile('<div>1</div>')($rootScope); +        var child2 = $compile('<div>2</div>')($rootScope); +        element.append(child1); +        element.append(child2); +        expect(element.text()).toBe('12'); +        $animate.move(child1, element, child2); +        expect(element.text()).toBe('21'); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +      })); + +      it("should animate the show animation event", inject(function($animate, $rootScope, $sniffer) { +        $rootScope.$digest(); +        element.addClass('ng-hide'); +        expect(element).toBeHidden(); +        $animate.show(element); +        if($sniffer.transitions) { +          expect(element.hasClass('ng-hide-remove')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('ng-hide-remove-active')).toBe(true); +          window.setTimeout.expect(0).process(); +        } +        expect(element).toBeShown(); +      })); + +      it("should animate the hide animation event", inject(function($animate, $rootScope, $sniffer) { +        $rootScope.$digest(); +        expect(element).toBeShown(); +        $animate.hide(element); +        if($sniffer.transitions) { +          expect(element.hasClass('ng-hide-add')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('ng-hide-add-active')).toBe(true); +          window.setTimeout.expect(0).process(); +        } +        expect(element).toBeHidden(); +      })); + +      it("should assign the ngAnimate string to all events if a string is given", +        inject(function($animate, $sniffer, $rootScope) { + +        if (!$sniffer.transitions) return; + +        $rootScope.$digest(); + +        //enter +        $animate.enter(child, element); +        expect(child.attr('class')).toContain('ng-enter'); +        window.setTimeout.expect(1).process(); +        expect(child.attr('class')).toContain('ng-enter-active'); +        window.setTimeout.expect(0).process(); + +        //leave +        element.append(after); +        $animate.move(child, element, after); +        expect(child.attr('class')).toContain('ng-move'); +        window.setTimeout.expect(1).process(); +        expect(child.attr('class')).toContain('ng-move-active'); +        window.setTimeout.expect(0).process(); + +        //hide +        $animate.hide(child); +        expect(child.attr('class')).toContain('ng-hide-add'); +        window.setTimeout.expect(1).process(); +        expect(child.attr('class')).toContain('ng-hide-add-active'); +        window.setTimeout.expect(0).process(); + +        //show +        $animate.show(child); +        expect(child.attr('class')).toContain('ng-hide-remove'); +        window.setTimeout.expect(1).process(); +        expect(child.attr('class')).toContain('ng-hide-remove-active'); +        window.setTimeout.expect(0).process(); + +        //leave +        $animate.leave(child); +        expect(child.attr('class')).toContain('ng-leave'); +        window.setTimeout.expect(1).process(); +        expect(child.attr('class')).toContain('ng-leave-active'); +        window.setTimeout.expect(0).process(); +      })); + +      it("should not run if animations are disabled", inject(function($animate, $rootScope, $sniffer) { +        $animate.enabled(false); + +        $rootScope.$digest(); + +        element.addClass('setup-memo'); + +        element.text('123'); +        expect(element.text()).toBe('123'); +        $animate.show(element); +        expect(element.text()).toBe('123'); + +        $animate.enabled(true); + +        $animate.show(element); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        expect(element.text()).toBe('memento'); +      })); + +      it("should only call done() once and right away if another animation takes place in between", +        inject(function($animate, $rootScope, $sniffer) { + +        element.append(child); +        child.addClass('custom-delay'); + +        expect(element).toBeShown(); +        $animate.hide(child); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        expect(child).toBeShown(); + +        $animate.leave(child); +        expect(child).toBeHidden(); //hides instantly + +        //lets change this to prove that done doesn't fire anymore for the previous hide() operation +        child.css('display','block');  +        child.removeClass('ng-hide'); + +        window.setTimeout.expect(2000).process(); +        if($sniffer.transitions) { +          window.setTimeout.expect(0).process(); +        } +        expect(child).toBeShown(); + +        expect(element.children().length).toBe(1); //still animating + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        window.setTimeout.expect(2000).process(); +        if($sniffer.transitions) { +          window.setTimeout.expect(0).process(); +        } +        expect(element.children().length).toBe(0); +      })); + +      it("should call the cancel callback when another animation is called on the same element", +        inject(function($animate, $rootScope, $sniffer) { + +        element.append(child); + +        child.addClass('custom-delay ng-hide'); +        $animate.show(child); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } + +        $animate.hide(child); + +        expect(child.hasClass('animation-cancelled')).toBe(true); +      })); + + +      it("should NOT clobber all data on an element when animation is finished", +        inject(function($animate, $rootScope, $sniffer) { + +        child.css('display','none'); +        element.data('foo', 'bar'); + +        $animate.show(element); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } + +        $animate.hide(element); + +        expect(element.data('foo')).toEqual('bar'); +      })); + + +      it("should allow multiple JS animations which run in parallel", +        inject(function($animate, $rootScope, $compile, $sniffer) { + +          $animate.addClass(element, 'custom-delay custom-long-delay'); +          if($sniffer.transitions) { +            expect(element[0].className).toContain('custom-delay-add custom-long-delay-add'); +            window.setTimeout.expect(1).process(); +            expect(element[0].className).toContain('custom-delay-add-active custom-long-delay-add-active'); +          } +          window.setTimeout.expect(2000).process(); +          window.setTimeout.expect(20000).process(); +          if($sniffer.transitions) { +            window.setTimeout.expect(0).process(); //css animation +          } + +          expect(element.hasClass('custom-delay')).toBe(true); +          expect(element.hasClass('custom-delay-add')).toBe(false); +          expect(element.hasClass('custom-delay-add-active')).toBe(false); + +          expect(element.hasClass('custom-long-delay')).toBe(true); +          expect(element.hasClass('custom-long-delay-add')).toBe(false); +          expect(element.hasClass('custom-long-delay-add-active')).toBe(false); +      })); + +      it("should allow both multiple JS and CSS animations which run in parallel", +        inject(function($animate, $rootScope, $compile, $sniffer, _$rootElement_) { +        $rootElement = _$rootElement_; + +        var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +        var style = 'transition: 1s linear all;' + +                    vendorPrefix + 'transition: 1s linear all;' + +        element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); +        element.addClass('custom-delay custom-long-delay'); +        $rootScope.$digest(); + +        $animate.show(element);  + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        window.setTimeout.expect(2000).process(); //1st JavaScript Animation +        window.setTimeout.expect(20000).process(); //2nd JavaScript Animation +        if($sniffer.transitions) { +          window.setTimeout.expect(1000).process(); //CSS animation +        } + +        expect(element.hasClass('custom-delay')).toBe(true); +        expect(element.hasClass('custom-delay-add')).toBe(false); +        expect(element.hasClass('custom-delay-add-active')).toBe(false); + +        expect(element.hasClass('custom-long-delay')).toBe(true); +        expect(element.hasClass('custom-long-delay-add')).toBe(false); +        expect(element.hasClass('custom-long-delay-add-active')).toBe(false); +      })); +    }); + +    describe("with CSS3", function() { +      var window, prefix, vendorPrefix; + +      beforeEach(function() { +        module(function($animateProvider, $provide) { +          $provide.value('$window', window = angular.mock.createMockWindow()); +          return function($sniffer, _$rootElement_, $animate) { +            vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +            $rootElement = _$rootElement_; +          }; +        }) +      }); + +      describe("Animations", function() { +        it("should properly detect and make use of CSS Animations", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          var style = 'animation: some_animation 4s linear 0s 1 alternate;' + +                      vendorPrefix + 'animation: some_animation 4s linear 0s 1 alternate;'; +          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); + +          $animate.show(element); +          if ($sniffer.animations) { +            window.setTimeout.expect(1).process(); +            window.setTimeout.expect(4000).process(); +          } +          else { +            expect(window.setTimeout.queue.length).toBe(0); +          } +          expect(element).toBeShown(); +        })); + +        it("should properly detect and make use of CSS Animations with multiple iterations", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          var style = 'animation-duration: 2s;' +  +                      'animation-iteration-count: 3;' + +                      vendorPrefix + 'animation-duration: 2s;' +  +                      vendorPrefix + 'animation-iteration-count: 3;'; +          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); + +          $animate.show(element); +          if ($sniffer.animations) { +            window.setTimeout.expect(1).process(); +            window.setTimeout.expect(6000).process(); +          } +          else { +            expect(window.setTimeout.queue.length).toBe(0); +          } +          expect(element).toBeShown(); +        })); + +        it("should fallback to the animation duration if an infinite iteration is provided", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          var style = 'animation-duration: 2s;' +  +                      'animation-iteration-count: infinite;' + +                      vendorPrefix + 'animation-duration: 2s;' +  +                      vendorPrefix + 'animation-iteration-count: infinite;'; +          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); + +          $animate.show(element); +          if ($sniffer.animations) { +            window.setTimeout.expect(1).process(); +            window.setTimeout.expect(2000).process(); +          } +          else { +            expect(window.setTimeout.queue.length).toBe(0); +          } +          expect(element).toBeShown(); +        })); + +        it("should consider the animation delay is provided", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          var style = 'animation-duration: 2s;' +  +                      'animation-delay: 10s;' + +                      'animation-iteration-count: 5;' + +                      vendorPrefix + 'animation-duration: 2s;' +  +                      vendorPrefix + 'animation-delay: 10s;' + +                      vendorPrefix + 'animation-iteration-count: 5;'; +          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); + +          $animate.show(element); +          if ($sniffer.transitions) { +            window.setTimeout.expect(1).process(); +            window.setTimeout.expect(20000).process(); +          } +          else { +            expect(window.setTimeout.queue.length).toBe(0); +          } +          expect(element).toBeShown(); +        })); + +        it("should skip animations if disabled and run when enabled", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          $animate.enabled(false); +          var style = 'animation: some_animation 2s linear 0s 1 alternate;' + +                      vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;' + +          element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); +          $animate.show(element); +          expect(element).toBeShown(); +        })); + +        it("should finish the previous animation when a new animation is started", +          inject(function($animate, $rootScope, $compile, $sniffer) { +            var style = 'animation: some_animation 2s linear 0s 1 alternate;' + +                        vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;' + +            element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); +            element.addClass('custom'); + +            $animate.show(element); +            if($sniffer.animations) { +              window.setTimeout.expect(1).process(); +              expect(element.hasClass('ng-hide-remove')).toBe(true); +              expect(element.hasClass('ng-hide-remove-active')).toBe(true); +            } +            else { //animation is skipped +              expect(window.setTimeout.queue.length).toBe(0); +            } + +            $animate.hide(element); +            expect(element.hasClass('ng-hide-remove')).toBe(false); //added right away + +            if($sniffer.animations) { //cleanup some pending animations +              window.setTimeout.expect(2000).process(); +              window.setTimeout.expect(1).process(); +              expect(element.hasClass('ng-hide-add')).toBe(true); +              expect(element.hasClass('ng-hide-add-active')).toBe(true); +            } + +            expect(element.hasClass('ng-hide-remove-active')).toBe(false); +        })); +      }); + +      describe("Transitions", function() { +        it("should skip transitions if disabled and run when enabled", +            inject(function($animate, $rootScope, $compile, $sniffer) { +          $animate.enabled(false); +          element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); +          $animate.show(element); +          expect(element).toBeShown(); + +          $animate.enabled(true); + +          element.addClass('ng-hide'); +          expect(element).toBeHidden(); + +          $animate.show(element); +          if ($sniffer.transitions) { +            window.setTimeout.expect(1).process(); +            window.setTimeout.expect(1000).process(); +          } +          else { +            expect(window.setTimeout.queue.length).toBe(0); +          } +          expect(element).toBeShown(); +        })); + +        it("should skip animations if disabled and run when enabled picking the longest specified duration", +          inject(function($animate, $rootScope, $compile, $sniffer) { +            element = $compile(html('<div style="' + vendorPrefix + 'transition-duration: 1s, 2000ms, 1s; ' + vendorPrefix + 'transition-property: height, left, opacity">foo</div>'))($rootScope); +            element.addClass('ng-hide'); +            $animate.show(element); +            if ($sniffer.transitions) { +              expect(element).toBeHidden(); +              window.setTimeout.expect(1).process(); +              window.setTimeout.expect(2000).process(); +            } +            else { +              expect(window.setTimeout.queue.length).toBe(0); +            } +            expect(element).toBeShown(); +          })); + +        it("should skip animations if disabled and run when enabled picking the longest specified duration/delay combination", +          inject(function($animate, $rootScope, $compile, $sniffer) { +            $animate.enabled(false); +            element = $compile(html('<div style="' + vendorPrefix +  +              'transition-duration: 1s, 0s, 1s; ' + vendorPrefix + +              'transition-delay: 2s, 1000ms, 2s; ' + vendorPrefix +  +              'transition-property: height, left, opacity">foo</div>'))($rootScope); + +            element.addClass('ng-hide'); +            $animate.show(element); +            expect(element).toBeShown(); + +            $animate.enabled(true); + +            element.addClass('ng-hide'); +            expect(element).toBeHidden(); + +            $animate.show(element); +            if ($sniffer.transitions) { +              window.setTimeout.expect(1).process(); +              window.setTimeout.expect(3000).process(); +            } +            else { +              expect(window.setTimeout.queue.length).toBe(0); +            } +            expect(element).toBeShown(); +        })); + +        it("should select the highest duration and delay", +          inject(function($animate, $rootScope, $compile, $sniffer) { +            var styles = 'transition:1s linear all 2s;' +  +                         vendorPrefix + 'transition:1s linear all 2s;' +  +                         'animation:my_ani 10s 1s;' +  +                         vendorPrefix + 'animation:my_ani 10s 1s;'; + +            element = $compile(html('<div style="' + styles + '">foo</div>'))($rootScope); + +            element.addClass('ng-hide'); +            expect(element).toBeHidden(); + +            $animate.show(element); +            if ($sniffer.transitions) { +              window.setTimeout.expect(1).process(); +              window.setTimeout.expect(11000).process(); +            } +            else { +              expect(window.setTimeout.queue.length).toBe(0); +            } +            expect(element).toBeShown(); +        })); + +        it("should finish the previous transition when a new animation is started", +          inject(function($animate, $rootScope, $compile, $sniffer) { +            var style = 'transition: 1s linear all;' + +                        vendorPrefix + 'transition: 1s linear all;' + +            element = $compile(html('<div style="' + style + '">1</div>'))($rootScope); + +            element.addClass('ng-hide'); +            $animate.show(element); +            if($sniffer.transitions) { +              expect(element.hasClass('ng-hide-remove')).toBe(true); +              window.setTimeout.expect(1).process(); +              expect(element.hasClass('ng-hide-remove-active')).toBe(true); +              window.setTimeout.expect(1000).process(); +            } +            else { //animation is skipped +              expect(window.setTimeout.queue.length).toBe(0); +            } +            expect(element.hasClass('ng-hide-remove')).toBe(false); +            expect(element.hasClass('ng-hide-remove-active')).toBe(false); +            expect(element).toBeShown(); + +            $animate.hide(element); +            if($sniffer.transitions) { +              expect(element.hasClass('ng-hide-add')).toBe(true); +              window.setTimeout.expect(1).process(); +              expect(element.hasClass('ng-hide-add-active')).toBe(true); +            } +            else { +              expect(window.setTimeout.queue.length).toBe(0); +            } +        })); +      }); +    }); + +    describe('animation evaluation', function () { +      beforeEach(module(function($provide) { +        $provide.value('$window', window = angular.mock.createMockWindow()); +      })); + +      it('should re-evaluate the CSS classes for an animation each time', +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        angular.element(document.body).append($rootElement); + +        element[0].className = 'abc'; +        $animate.enter(element, parent); +        if ($sniffer.transitions) { +          expect(element.hasClass('abc ng-enter')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true); +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('abc')).toBe(true); + +        element[0].className = 'xyz'; +        $animate.enter(element, parent); +        if ($sniffer.transitions) { +          expect(element.hasClass('xyz')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true); +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('xyz')).toBe(true); +      })); + +      it('should only append active to the newly append CSS className values', +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        angular.element(document.body).append($rootElement); + +        element.attr('class','one two'); + +        $animate.enter(element, parent); +        if($sniffer.transitions) { +          expect(element.hasClass('one two ng-enter')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true); +          expect(element.hasClass('one-active')).toBe(false); +          expect(element.hasClass('two-active')).toBe(false); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        expect(element.hasClass('one two')).toBe(true); +      })); +    }); + +    describe("Callbacks", function() { + +      var window, vendorPrefix; +      beforeEach(function() { +        module(function($animateProvider, $provide) { +          $provide.value('$window', window = angular.mock.createMockWindow()); +          $animateProvider.register('.custom', function() { +            return { +              show : function(element, done) { +                window.setTimeout(done, 2000); +              } +            } +          }); +          $animateProvider.register('.other', function() { +            return { +              start : function(element, done) { +                window.setTimeout(done, 10000); +              } +            } +          }); +        }) +        inject(function($sniffer, $animate) { +          vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +        }); +      }); + +      it("should fire the enter callback", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        body.append($rootElement); + +        var flag = false; +        $animate.enter(element, parent, null, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } +        expect(flag).toBe(true); +      })); + +      it("should fire the leave callback", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        body.append($rootElement); + +        var flag = false; +        $animate.leave(element, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } +        expect(flag).toBe(true); +      })); + +      it("should fire the move callback", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var parent2 = jqLite('<div id="nice"></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        body.append($rootElement); + +        var flag = false; +        $animate.move(element, parent, parent2, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        expect(flag).toBe(true); +        expect(element.parent().id).toBe(parent2.id); +      })); + +      it("should fire the addClass/removeClass callbacks", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        body.append($rootElement); + +        var signature = ''; +        $animate.addClass(element, 'on', function() { +          signature += 'A'; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        $animate.removeClass(element, 'on', function() { +          signature += 'B'; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        expect(signature).toBe('AB'); +      })); + +      it("should fire a done callback when provided with no animation", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        var element = parent.find('span'); +        $rootElement.append(parent); +        body.append($rootElement); + +        var flag = false; +        $animate.show(element, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } +        expect(flag).toBe(true); +      })); + +      it("should fire a done callback when provided with a css animation/transition", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var transition = 'transition:1s linear all;'; +        var style = transition + ' ' + vendorPrefix + transition; +        var parent = jqLite('<div><span style="' + style + '"></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = parent.find('span'); + +        var flag = false; +        $animate.show(element, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(1000).process(); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } +        expect(flag).toBe(true); +      })); + +      it("should fire a done callback when provided with a JS animation", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = parent.find('span'); +        element.addClass('custom'); + +        var flag = false; +        $animate.show(element, function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        window.setTimeout.expect(2000).process(); +        if($sniffer.transitions) { +          window.setTimeout.expect(0).process(); +        } +        expect(flag).toBe(true); +      })); + +      it("should fire the callback right away if another animation is called right after", +        inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = parent.find('span'); + +        var signature = ''; +        $animate.show(element, function() { +          signature += 'A'; +        }); +        $animate.hide(element, function() { +          signature += 'B'; +        }); + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        $animate.hide(element); //earlier animation cancelled +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        expect(signature).toBe('AB'); +      })); +    }); + +    describe("addClass / removeClass", function() { + +      var window, vendorPrefix; +      beforeEach(function() { +        module(function($animateProvider, $provide) { +          $provide.value('$window', window = angular.mock.createMockWindow()); +          $animateProvider.register('.klassy', function() { +            return { +              addClass : function(element, className, done) { +                window.setTimeout(done, 500); +              }, +              removeClass : function(element, className, done) { +                window.setTimeout(done, 3000); +              } +            } +          }); +        }) +        inject(function($sniffer, $animate) { +          vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +        }); +      }); + +      it("should add and remove CSS classes after an animation even if no animation is present", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        $animate.addClass(element,'klass'); + +        if($sniffer.transitions) { +          expect(element.hasClass('klass-add')).toBe(true); +        } + +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('klass-add')).toBe(true); +          expect(element.hasClass('klass-add-active')).toBe(true); +          expect(element.hasClass('klass')).toBe(false); + +          window.setTimeout.expect(0).process(); +          expect(element.hasClass('klass-add')).toBe(false); +          expect(element.hasClass('klass-add-active')).toBe(false); +        } +        expect(element.hasClass('klass')).toBe(true); + +        $animate.removeClass(element,'klass'); + +        if($sniffer.transitions) { +          expect(element.hasClass('klass')).toBe(true); +          expect(element.hasClass('klass-remove')).toBe(true); + +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('klass')).toBe(true); +          expect(element.hasClass('klass-remove')).toBe(true); +          expect(element.hasClass('klass-remove-active')).toBe(true); + +          window.setTimeout.expect(0).process(); +        } + +        expect(element.hasClass('klass')).toBe(false); +        expect(element.hasClass('klass-remove')).toBe(false); +        expect(element.hasClass('klass-remove-active')).toBe(false); +      })); + +      it("should add and remove CSS classes with a callback", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        var signature = ''; + +        $animate.addClass(element,'klass', function() { +          signature += 'A'; +        }); + +        if($sniffer.transitions) { +          expect(element.hasClass('klass')).toBe(false); +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('klass')).toBe(true); + +        $animate.removeClass(element,'klass', function() { +          signature += 'B'; +        }); + +        if($sniffer.transitions) { +          expect(element.hasClass('klass')).toBe(true); +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('klass')).toBe(false); + +        expect(signature).toBe('AB'); +      })); + +      it("should end the current addClass animation, add the CSS class and then run the removeClass animation", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        var signature = ''; + +        $animate.addClass(element,'klass', function() { +          signature += '1'; +        }); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('klass')).toBe(false); +          expect(element.hasClass('klass-add')).toBe(true); +        } + +        //this cancels out the older animation +        $animate.removeClass(element,'klass', function() { +          signature += '2'; +        }); + +        if($sniffer.transitions) { +          expect(element.hasClass('klass')).toBe(true); +          expect(element.hasClass('klass-add')).toBe(false); +          expect(element.hasClass('klass-add-active')).toBe(false); + +          expect(element.hasClass('klass-remove')).toBe(true); +          window.setTimeout.expect(0).process(); +          window.setTimeout.expect(1).process(); +          window.setTimeout.expect(0).process(); +        } + +        expect(element.hasClass('klass')).toBe(false); +        expect(signature).toBe('12'); +      })); + +      it("should properly execute JS animations and use callbacks when using addClass / removeClass", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { +        var parent = jqLite('<div><span></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        var signature = ''; + +        $animate.addClass(element,'klassy', function() { +          signature += 'X'; +        }); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        window.setTimeout.expect(500).process(); +        if($sniffer.transitions) { +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('klassy')).toBe(true); + +        $animate.removeClass(element,'klassy', function() { +          signature += 'Y'; +        }); +        if($sniffer.transitions) { +          window.setTimeout.expect(1).process(); +        } +        window.setTimeout.expect(3000).process(); +        if($sniffer.transitions) { +          window.setTimeout.expect(0).process(); +        } +        expect(element.hasClass('klassy')).toBe(false); + +        expect(signature).toBe('XY'); +      })); + +      it("should properly execute CSS animations/transitions and use callbacks when using addClass / removeClass", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var transition = 'transition:11s linear all;'; +        var style = transition + ' ' + vendorPrefix + transition; +        var parent = jqLite('<div><span style="' + style + '"></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        var signature = ''; + +        $animate.addClass(element,'klass', function() { +          signature += 'd'; +        }); +        if($sniffer.transitions) { +          expect(element.hasClass('klass-add')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('klass-add-active')).toBe(true); +          window.setTimeout.expect(11000).process(); +          expect(element.hasClass('klass-add')).toBe(false); +          expect(element.hasClass('klass-add-active')).toBe(false); +        } +        expect(element.hasClass('klass')).toBe(true); + +        $animate.removeClass(element,'klass', function() { +          signature += 'b'; +        }); +        if($sniffer.transitions) { +          expect(element.hasClass('klass-remove')).toBe(true); +          window.setTimeout.expect(1).process(); +          expect(element.hasClass('klass-remove-active')).toBe(true); +          window.setTimeout.expect(11000).process(); +          expect(element.hasClass('klass-remove')).toBe(false); +          expect(element.hasClass('klass-remove-active')).toBe(false); +        } +        expect(element.hasClass('klass')).toBe(false); + +        expect(signature).toBe('db'); +      })); + +      it("should allow for multiple css classes to be animated plus a callback when added", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var transition = 'transition:7s linear all;'; +        var style = transition + ' ' + vendorPrefix + transition; +        var parent = jqLite('<div><span style="' + style + '"></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        var flag = false; +        $animate.addClass(element,'one two', function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          expect(element.hasClass('one-add')).toBe(true); +          expect(element.hasClass('two-add')).toBe(true); +          window.setTimeout.expect(1).process(); + +          expect(element.hasClass('one-add-active')).toBe(true); +          expect(element.hasClass('two-add-active')).toBe(true); +          window.setTimeout.expect(7000).process(); + +          expect(element.hasClass('one-add')).toBe(false); +          expect(element.hasClass('one-add-active')).toBe(false); +          expect(element.hasClass('two-add')).toBe(false); +          expect(element.hasClass('two-add-active')).toBe(false); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        expect(element.hasClass('one')).toBe(true); +        expect(element.hasClass('two')).toBe(true); + +        expect(flag).toBe(true); +      })); + +      it("should allow for multiple css classes to be animated plus a callback when removed", +        inject(function($animate, $rootScope, $sniffer, $rootElement) { + +        var transition = 'transition:9s linear all;'; +        var style = transition + ' ' + vendorPrefix + transition; +        var parent = jqLite('<div><span style="' + style + '"></span></div>'); +        $rootElement.append(parent); +        body.append($rootElement); +        var element = jqLite(parent.find('span')); + +        element.addClass('one two'); +        expect(element.hasClass('one')).toBe(true); +        expect(element.hasClass('two')).toBe(true); + +        var flag = false; +        $animate.removeClass(element,'one two', function() { +          flag = true; +        }); + +        if($sniffer.transitions) { +          expect(element.hasClass('one-remove')).toBe(true); +          expect(element.hasClass('two-remove')).toBe(true); +          window.setTimeout.expect(1).process(); + +          expect(element.hasClass('one-remove-active')).toBe(true); +          expect(element.hasClass('two-remove-active')).toBe(true); +          window.setTimeout.expect(9000).process(); + +          expect(element.hasClass('one-remove')).toBe(false); +          expect(element.hasClass('one-remove-active')).toBe(false); +          expect(element.hasClass('two-remove')).toBe(false); +          expect(element.hasClass('two-remove-active')).toBe(false); +        } +        else { +          expect(window.setTimeout.queue.length).toBe(0); +        } + +        expect(element.hasClass('one')).toBe(false); +        expect(element.hasClass('two')).toBe(false); + +        expect(flag).toBe(true); +      })); +    }); +  }); +     +  var $rootElement, $document, window; +  beforeEach(module(function($provide) { +    $provide.value('$window', window = angular.mock.createMockWindow()); + +    return function(_$rootElement_, _$document_, $animate) { +      $rootElement = _$rootElement_; +      $document = _$document_; +      $animate.enabled(true); +    } +  })); + +  function html(element) { +    var body = jqLite($document[0].body); +    $rootElement.append(element); +    body.append($rootElement); +    return element; +  } + +  it("should properly animate and parse CSS3 transitions", +    inject(function($compile, $rootScope, $animate, $sniffer) { + +    var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +    var style = 'transition: 1s linear all;' + +                vendorPrefix + 'transition: 1s linear all;'; +    var element = html($compile('<div>...</div>')($rootScope)); +    var child = $compile('<div style="' + style + '">...</div>')($rootScope); + +    $animate.enter(child, element); + +    if($sniffer.transitions) { +      expect(child.hasClass('ng-enter')).toBe(true); +      window.setTimeout.expect(1).process(); +      expect(child.hasClass('ng-enter-active')).toBe(true); +      window.setTimeout.expect(1000).process(); +    } +    expect(child.hasClass('ng-enter')).toBe(false); +    expect(child.hasClass('ng-enter-active')).toBe(false); +  })); + +  it("should properly animate and parse CSS3 animations", +    inject(function($compile, $rootScope, $animate, $sniffer) { + +    var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +    var style = 'animation: some_animation 4s linear 1s 2 alternate;' + +                vendorPrefix + 'animation: some_animation 4s linear 1s 2 alternate;'; +    var element = html($compile('<div>...</div>')($rootScope)); +    var child = $compile('<div style="' + style + '">...</div>')($rootScope); +    $animate.enter(child, element); + +    if($sniffer.transitions) { +      expect(child.hasClass('ng-enter')).toBe(true); +      window.setTimeout.expect(1).process(); +      expect(child.hasClass('ng-enter-active')).toBe(true); +      window.setTimeout.expect(9000).process(); +    } +    expect(child.hasClass('ng-enter')).toBe(false); +    expect(child.hasClass('ng-enter-active')).toBe(false); +  })); + +  it("should skip animations if the browser does not support CSS3 transitions and CSS3 animations", +    inject(function($compile, $rootScope, $animate, $sniffer) { + +    $sniffer.animations = false; +    $sniffer.transitions = false; + +    var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +    var style = 'animation: some_animation 4s linear 1s 2 alternate;' + +                vendorPrefix + 'animation: some_animation 4s linear 1s 2 alternate;'; +    var element = html($compile('<div>...</div>')($rootScope)); +    var child = $compile('<div style="' + style + '">...</div>')($rootScope); + +    expect(child.hasClass('ng-enter')).toBe(false); +    $animate.enter(child, element); +    expect(window.setTimeout.queue.length).toBe(0); +    expect(child.hasClass('ng-enter')).toBe(false); +  })); + +  it("should run other defined animations inline with CSS3 animations", function() { +    module(function($animateProvider) { +      $animateProvider.register('.custom', function($window) { +        return { +          enter : function(element, done) { +            element.addClass('i-was-animated'); +            $window.setTimeout(done, 10); +          } +        } +      }); +    }) +    inject(function($compile, $rootScope, $animate, $sniffer) { +      var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +      var style = 'transition: 1s linear all;' + +                  vendorPrefix + 'transition: 1s linear all;'; +      var element = html($compile('<div>...</div>')($rootScope)); +      var child = $compile('<div style="' + style + '">...</div>')($rootScope); + +      expect(child.hasClass('i-was-animated')).toBe(false); + +      child.addClass('custom'); +      $animate.enter(child, element); + +      expect(child.hasClass('ng-enter')).toBe(true); +      if($sniffer.transitions) { +        window.setTimeout.expect(1).process(); +        expect(child.hasClass('ng-enter-active')).toBe(true); +      } + +      window.setTimeout.expect(10).process(); + +      if($sniffer.transitions) { +        expect(child.hasClass('ng-enter-active')).toBe(true); +        window.setTimeout.expect(1000).process(); +        expect(child.hasClass('ng-enter')).toBe(false); +        expect(child.hasClass('ng-enter-active')).toBe(false); +      } + +      expect(child.hasClass('i-was-animated')).toBe(true); +    }); +  }); + +  it("should properly cancel CSS transitions or animations if another animation is fired", function() { +    module(function($animateProvider) { +      $animateProvider.register('.usurper', function($window) { +        return { +          leave : function(element, done) { +            element.addClass('this-is-mine-now'); +            $window.setTimeout(done, 55); +          } +        } +      }); +    }); +    inject(function($compile, $rootScope, $animate, $sniffer) { +      var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +      var style = 'transition: 2s linear all;' + +                  vendorPrefix + 'transition: 2s linear all;'; +      var element = html($compile('<div>...</div>')($rootScope)); +      var child = $compile('<div style="' + style + '">...</div>')($rootScope); + +      $animate.enter(child, element); + +      if($sniffer.transitions) { +        expect(child.hasClass('ng-enter')).toBe(true); +      } + +      expect(child.hasClass('this-is-mine-now')).toBe(false); +      child.addClass('usurper'); +      $animate.leave(child); + +      expect(child.hasClass('ng-enter')).toBe(false); +      expect(child.hasClass('ng-enter-active')).toBe(false); + +      expect(child.hasClass('usurper')).toBe(true); +      expect(child.hasClass('this-is-mine-now')).toBe(true); +      if($sniffer.transitions) { +        window.setTimeout.expect(1).process(); +        window.setTimeout.expect(1).process(); +      } +      window.setTimeout.expect(55).process(); +      if($sniffer.transitions) { +        window.setTimeout.expect(2000).process(); + +        //even though this exists, the animation will still not happen +        //since the done function has already been called in the cancellation +        window.setTimeout.expect(2000).process(); +      } + +      expect(child.hasClass('usurper-active')).toBe(false); +    }); +  }); + +  it("should add and remove CSS classes and perform CSS animations during the process", +    inject(function($compile, $rootScope, $animate, $sniffer) { + +    var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +    var style = 'transition: 10s linear all;' + +                vendorPrefix + 'transition: 10s linear all;'; +    var element = html($compile('<div style="' + style + '"></div>')($rootScope)); + +    expect(element.hasClass('on')).toBe(false); + +    $animate.addClass(element, 'on'); + +    if($sniffer.transitions) { +      expect(element.hasClass('on')).toBe(false); +      expect(element.hasClass('on-add')).toBe(true); +      window.setTimeout.expect(1).process(); +      expect(element.hasClass('on-add-active')).toBe(true); +      window.setTimeout.expect(10000).process(); +    } + +    expect(element.hasClass('on')).toBe(true); +    expect(element.hasClass('on-add')).toBe(false); +    expect(element.hasClass('on-add-active')).toBe(false); + +    $animate.removeClass(element, 'on'); +    if($sniffer.transitions) { +      expect(element.hasClass('on')).toBe(true); +      expect(element.hasClass('on-remove')).toBe(true); +      window.setTimeout.expect(1).process(); +      expect(element.hasClass('on-remove-active')).toBe(true); +      window.setTimeout.expect(10000).process(); +    } + +    expect(element.hasClass('on')).toBe(false); +    expect(element.hasClass('on-remove')).toBe(false); +    expect(element.hasClass('on-remove-active')).toBe(false); +  })); + +  it("should show and hide elements with CSS & JS animations being performed in the process", function() { +    module(function($animateProvider) { +      $animateProvider.register('.displayer', function($window) { +        return { +          show : function(element, done) { +            element.removeClass('hiding'); +            element.addClass('showing'); +            $window.setTimeout(done, 25); +          }, +          hide : function(element, done) { +            element.removeClass('showing'); +            element.addClass('hiding'); +            $window.setTimeout(done, 555); +          } +        } +      }); +    }) +    inject(function($compile, $rootScope, $animate, $sniffer) { +      var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-'; +      var style = 'transition: 5s linear all;' + +                  vendorPrefix + 'transition: 5s linear all;'; +      var element = html($compile('<div style="' + style + '"></div>')($rootScope)); + +      element.addClass('displayer'); + +      expect(element).toBeShown(); +      expect(element.hasClass('showing')).toBe(false); +      expect(element.hasClass('hiding')).toBe(false); + +      $animate.hide(element); + +      if($sniffer.transitions) { +        expect(element).toBeShown(); //still showing +        window.setTimeout.expect(1).process(); +        expect(element).toBeShown(); +      } +      window.setTimeout.expect(555).process(); +      if($sniffer.transitions) { +        expect(element).toBeShown(); +        window.setTimeout.expect(5000).process(); +      } +      expect(element).toBeHidden(); + +      expect(element.hasClass('showing')).toBe(false); +      expect(element.hasClass('hiding')).toBe(true); +      $animate.show(element); + +      if($sniffer.transitions) { +        expect(element).toBeHidden(); +        window.setTimeout.expect(1).process(); +        expect(element).toBeHidden(); +      } +      window.setTimeout.expect(25).process(); +      if($sniffer.transitions) { +        expect(element).toBeHidden(); +        window.setTimeout.expect(5000).process(); +      } +      expect(element).toBeShown(); + +      expect(element.hasClass('showing')).toBe(true); +      expect(element.hasClass('hiding')).toBe(false); +    }); +  }); + +}); diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index 50531c18..5f021f2d 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -7,9 +7,9 @@ describe('ngView', function() {    beforeEach(module(function($provide) {      $provide.value('$window', angular.mock.createMockWindow()); -    return function($rootScope, $compile, $animator) { +    return function($rootScope, $compile, $animate) {        element = $compile('<ng:view onload="load()"></ng:view>')($rootScope); -      $animator.enabled(true); +      $animate.enabled(true);      };    })); @@ -509,8 +509,7 @@ describe('ngView', function() {      });    }); -  describe('ngAnimate ', function() { -    var window, vendorPrefix; +  describe('animations', function() {      var body, element, $rootElement;      function html(html) { @@ -520,11 +519,6 @@ describe('ngView', function() {        return element;      } -    function applyCSS(element, cssProp, cssValue) { -      element.css(cssProp, cssValue); -      element.css(vendorPrefix + cssProp, cssValue); -    } -      beforeEach(module(function() {        // we need to run animation on attached elements;        return function(_$rootElement_) { @@ -540,128 +534,131 @@ describe('ngView', function() {      beforeEach(module(function($provide, $routeProvider) { -      $provide.value('$window', window = angular.mock.createMockWindow());        $routeProvider.when('/foo', {controller: noop, templateUrl: '/foo.html'}); -      return function($sniffer, $templateCache, $animator) { -        vendorPrefix = '-' + $sniffer.vendorPrefix + '-'; +      return function($templateCache) {          $templateCache.put('/foo.html', [200, '<div>data</div>', {}]); -        $animator.enabled(true);        }      })); -    it('should fire off the enter animation + add and remove the css classes', -        inject(function($compile, $rootScope, $sniffer, $location) { -          element = $compile(html('<div ng-view ng-animate="{enter: \'custom-enter\'}"></div>'))($rootScope); +    describe('hooks', function() { +      beforeEach(module('mock.animate')); -          $location.path('/foo'); -          $rootScope.$digest(); +      it('should fire off the enter animation', +          inject(function($compile, $rootScope, $location, $animate) { +            var item; +            element = $compile(html('<div ng-view></div>'))($rootScope); -          //if we add the custom css stuff here then it will get picked up before the animation takes place -          var child = jqLite(element.children()[0]); -          applyCSS(child, 'transition', '1s linear all'); +            $location.path('/foo'); +            $rootScope.$digest(); -          if ($sniffer.transitions) { -            expect(child.attr('class')).toContain('custom-enter'); -            window.setTimeout.expect(1).process(); - -            expect(child.attr('class')).toContain('custom-enter-active'); -            window.setTimeout.expect(1000).process(); -          } else { -            expect(window.setTimeout.queue).toEqual([]); -          } +            item = $animate.process('leave').element; +            item = $animate.process('leave').element; +            item = $animate.process('leave').element; -          expect(child.attr('class')).not.toContain('custom-enter'); -          expect(child.attr('class')).not.toContain('custom-enter-active'); -        })); - -    it('should fire off the leave animation + add and remove the css classes', -        inject(function($compile, $rootScope, $sniffer, $location, $templateCache) { -      $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]); -      element = $compile(html('<div ng-view ng-animate="{leave: \'custom-leave\'}"></div>'))($rootScope); - -      $location.path('/foo'); -      $rootScope.$digest(); +            item = $animate.process('enter').element; +            expect(item.text()).toBe('data'); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var child = jqLite(element.children()[0]); -      applyCSS(child, 'transition', '1s linear all'); +            item = $animate.process('leave').element; +            item = $animate.process('enter').element; +            expect(item.text()).toBe('data'); +          })); -      $location.path('/'); -      $rootScope.$digest(); +      it('should fire off the leave animation', +          inject(function($compile, $rootScope, $location, $templateCache, $animate) { -      if ($sniffer.transitions) { -        expect(child.attr('class')).toContain('custom-leave'); -        window.setTimeout.expect(1).process(); +        var item; +        $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]); +        element = $compile(html('<div ng-view></div>'))($rootScope); -        expect(child.attr('class')).toContain('custom-leave-active'); -        window.setTimeout.expect(1000).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } +        $location.path('/foo'); +        $rootScope.$digest(); -      expect(child.attr('class')).not.toContain('custom-leave'); -      expect(child.attr('class')).not.toContain('custom-leave-active'); -    })); +        item = $animate.process('leave').element; +        item = $animate.process('leave').element; +        item = $animate.process('leave').element; -    it('should catch and use the correct duration for animations', -        inject(function($compile, $rootScope, $sniffer, $location, $templateCache) { -      $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]); -      element = $compile(html( -          '<div ' + -              'ng-view ' + -              'ng-animate="{enter: \'customEnter\'}">' + -            '</div>' -      ))($rootScope); +        item = $animate.process('enter').element; +        expect(item.text()).toBe('foo'); -      $location.path('/foo'); -      $rootScope.$digest(); +        item = $animate.process('leave').element; +        item = $animate.process('enter').element; +        expect(item.text()).toBe('foo'); -      //if we add the custom css stuff here then it will get picked up before the animation takes place -      var child = jqLite(element.children()[0]); -      applyCSS(child, 'transition', '0.5s linear all'); +        $location.path('/'); +        $rootScope.$digest(); -      if($sniffer.transitions) { -        window.setTimeout.expect(1).process(); -        window.setTimeout.expect($sniffer.transitions ? 500 : 0).process(); -      } else { -        expect(window.setTimeout.queue).toEqual([]); -      } -    })); +        item = $animate.process('leave').element; +        expect(item.text()).toBe('foo'); +        item = $animate.process('leave').element; +        expect(item.text()).toBe('foo'); +      })); +    });      it('should not double compile when route changes', function() { -      module(function($routeProvider, $animationProvider, $provide) { +      module('ngAnimate'); +      module('mock.animate'); +      module(function($routeProvider, $animateProvider, $provide) {          $routeProvider.when('/foo', {template: '<div ng-repeat="i in [1,2]">{{i}}</div>'});          $routeProvider.when('/bar', {template: '<div ng-repeat="i in [3,4]">{{i}}</div>'}); -        $animationProvider.register('my-animation-leave', function() { +        $animateProvider.register('.my-animation', function() {            return { -            start: function(element, done) { +            leave: function(element, done) { +              dump('yes');                done();              }            };          });        }); -      inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer) { -        element = $compile(html('<ng:view onload="load()" ng-animate="\'my-animation\'"></ng:view>'))($rootScope); +      inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer, $animate) { +        if (!$sniffer.transitions) return; + +        element = $compile(html('<ng:view onload="load()"></ng:view>'))($rootScope);          $location.path('/foo');          $rootScope.$digest(); -        if ($sniffer.transitions) { -          $window.setTimeout.expect(1).process(); -          $window.setTimeout.expect(0).process(); -        } + +        $animate.process('leave'); +        $animate.process('leave'); +        $animate.process('leave'); +        $animate.process('enter'); +        $animate.process('leave'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter'); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(0).process(); +        $window.setTimeout.expect(0).process(); +        $window.setTimeout.expect(0).process(); +          expect(element.text()).toEqual('12');          $location.path('/bar');          $rootScope.$digest(); +        $animate.process('leave'); +        $animate.process('enter'); +        $animate.process('leave'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter'); +        $animate.process('enter');          expect(n(element.text())).toEqual('1234'); -        if ($sniffer.transitions) { -          $window.setTimeout.expect(1).process(); -          $window.setTimeout.expect(1).process(); -        } else { -          $window.setTimeout.expect(1).process(); -        } + +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(0).process(); +        $window.setTimeout.expect(0).process(); +        $window.setTimeout.expect(0).process(); +        $window.setTimeout.expect(0).process(); +          expect(element.text()).toEqual('34');          function n(text) { diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index d97d88a1..32dd75ea 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -233,7 +233,7 @@ function sortedHtml(element, showNgClass) {   */  function isCssVisible(node) {    var display = node.css('display'); -  return display != 'none'; +  return !node.hasClass('ng-hide') && display != 'none';  }  function assertHidden(node) { | 
