From 5825ac87715e3f1c42844a91438f05bef758d5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 12 Feb 2013 19:57:17 -0500 Subject: [PATCH] Basis for AngularJS animations added... --- angularFiles.js | 4 + animation-example/index.html | 158 +++++++++++++++++++ animation-example/index.js | 99 ++++++++++++ src/AngularPublic.js | 5 +- src/loader.js | 4 + src/ng/animation.js | 35 +++++ src/ng/directive/ngAnimate.js | 60 ++++++++ src/ng/directive/ngRepeat.js | 234 +++++++++++++++-------------- test/ng/animationControllerSpec.js | 117 +++++++++++++++ test/ng/animationSpec.js | 112 ++++++++++++++ test/ng/directive/ngAnimateSpec.js | 209 ++++++++++++++++++++++++++ test/testabilityPatch.js | 3 +- 12 files changed, 923 insertions(+), 117 deletions(-) create mode 100644 animation-example/index.html create mode 100644 animation-example/index.js create mode 100644 src/ng/animation.js create mode 100644 src/ng/directive/ngAnimate.js create mode 100644 test/ng/animationControllerSpec.js create mode 100644 test/ng/animationSpec.js create mode 100644 test/ng/directive/ngAnimateSpec.js diff --git a/angularFiles.js b/angularFiles.js index 47f18961f736..6e2576b7f476 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -30,12 +30,16 @@ angularFiles = { 'src/ng/locale.js', 'src/ng/timeout.js', + 'src/ng/animation.js', + 'src/ng/filter.js', 'src/ng/filter/filter.js', 'src/ng/filter/filters.js', 'src/ng/filter/limitTo.js', 'src/ng/filter/orderBy.js', + 'src/ng/directive/ngAnimate.js', + 'src/ng/directive/directives.js', 'src/ng/directive/a.js', 'src/ng/directive/booleanAttrs.js', diff --git a/animation-example/index.html b/animation-example/index.html new file mode 100644 index 000000000000..9dbfcfec01f1 --- /dev/null +++ b/animation-example/index.html @@ -0,0 +1,158 @@ + + + + + AngularJS Animation Demo + + + + +
+ + + +
+
+ Noop +
+
+
+
+ {{ $index + 1 }}: {{ item.name }} ({{ item.index }}) + +
+
+
+ +
+
+ Fade In / Fade Out +
+
+
+
+ {{ $index + 1 }}: {{ item.name }} ({{ item.index }}) + +
+
+
+ +
+
+ Slide In / Slide Out +
+
+
+
+ {{ $index + 1 }}: {{ item.name }} ({{ item.index }}) + +
+
+
+ +
+
+ ngRepeat with no ngAnimate directive at all... +
+
+
+
+ {{ $index + 1 }}: {{ item.name }} ({{ item.index }}) + +
+
+
+ +
+ + + + + + diff --git a/animation-example/index.js b/animation-example/index.js new file mode 100644 index 000000000000..e30a9937a9c6 --- /dev/null +++ b/animation-example/index.js @@ -0,0 +1,99 @@ +angular.module('Animator', []) + + .controller('AppCtrl', function($scope) { + $scope.add = function(name) { + if(!name) return; + $scope.items.push({ + index : $scope.items.length, + name : name + }); + }; + $scope.pop = function() { + $scope.items.pop(); + }; + $scope.remove = function(index) { + var found; + for(var i=0;i<$scope.items.length;i++) { + if($scope.items[i].index == index) { + found = i; + break; + } + } + $scope.items.splice(found, 1); + }; + $scope.sort = function(key) { + $scope.order = key; + }; + + var defaultItems = ["Afghanistan"," Albania"," Algeria"," American Samoa"," Andorra"," Angola"," Anguilla"," Antarctica"," Antigua and Barbuda"," Argentina"," Armenia"," Aruba"," Ashmore and Cartier"," Australia"," Austria"," Azerbaijan"," Bahrain"," Baker Island"," Bangladesh"," Barbados"," Bassas da India"," Belarus"," Belgium"," Belize"," Benin"," Bermuda"," Bhutan"," Bolivia"," Bosnia and Herzegovina"," Botswana"," Bouvet Island"," Brazil"," British Indian Ocean Territory"," British Virgin Islands"," Brunei Darussalam"," Bulgaria"," Burkina Faso"," Burma"," Burundi"," Cambodia"," Cameroon"," Canada"," Cape Verde"," Cayman Islands"," Central African Republic"," Chad"]; + + var i = 0; + $scope.items = []; + angular.forEach(defaultItems, function(item) { + $scope.items.push({ + index : i++, + name : item + }); + }); + $scope.order = 'index'; + }) + + .animation('fade-enter', function() { + return function(node, parent, after) { + after ? after.after(node) : parent.append(node); + node.css('opacity',0); + node.animate({ + 'opacity':1 + }); + }; + }) + + .animation('fade-move', function() { + return function(node, parent, after) { + //node.css('opacity',0); + after ? after.after(node) : parent.append(node); + node.animate({ + 'opacity':1 + }); + }; + }) + + .animation('fade-leave', function() { + return function(node, parent, after) { + node.fadeOut(function() { + node.remove(); + }); + }; + }) + + .animation('slide-enter', function() { + return function(node, parent, after) { + after ? after.after(node) : parent.append(node); + node.css({ + 'opacity':0, + 'position':'relative', + 'left':-100 + }); + node.animate({ + 'opacity':1, + 'left':0 + }); + }; + }) + + .animation('slide-leave', function() { + return function(node, parent, after) { + node.animate({ + 'opacity':0, + 'left':-100 + }, function() { + node.remove(); + }); + }; + }) + + .animation('slide-move', function() { + return function(node, parent, after) { + after ? after.after(node) : parent.append(node); + }; + }) diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 61c77af34c2e..f76bb9d41597 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -100,7 +100,8 @@ function publishExternalAPI(angular){ ngChange: ngChangeDirective, required: requiredDirective, ngRequired: requiredDirective, - ngValue: ngValueDirective + ngValue: ngValueDirective, + ngAnimate: ngAnimateDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); @@ -112,6 +113,7 @@ function publishExternalAPI(angular){ $document: $DocumentProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, + $animation: $AnimationProvider, $interpolate: $InterpolateProvider, $http: $HttpProvider, $httpBackend: $HttpBackendProvider, @@ -127,6 +129,7 @@ function publishExternalAPI(angular){ $timeout: $TimeoutProvider, $window: $WindowProvider }); + $provide.factory('$noopAnimator', $noopAnimatorFactory); } ]); } diff --git a/src/loader.js b/src/loader.js index ecb166085460..425007cb4b7f 100644 --- a/src/loader.js +++ b/src/loader.js @@ -163,6 +163,10 @@ function setupModuleLoader(window) { */ constant: invokeLater('$provide', 'constant', 'unshift'), + // + animation: invokeLater('$animationProvider', 'register'), + // + /** * @ngdoc method * @name angular.Module#filter diff --git a/src/ng/animation.js b/src/ng/animation.js new file mode 100644 index 000000000000..f23ad7a739c3 --- /dev/null +++ b/src/ng/animation.js @@ -0,0 +1,35 @@ +'use strict'; + +$AnimationProvider.$inject = ['$provide']; +function $AnimationProvider($provide) { + var suffix = 'Animation'; + var register = function(name, factory) { + $provide.factory(camelCase(name) + suffix, factory); + }; + + this.register = register; + + this.$get = function($injector) { + return function animationGetter(name) { + return $injector.get(camelCase(name) + suffix); + } + }; + + register('noopEnter', function() { + return function(node, parent, after) { + after ? after.after(node) : parent.append(node); + }; + }); + + register('noopLeave', function() { + return function(node, parent, after) { + node.remove(); + }; + }); + + register('noopMove', function() { + return function(node, parent, after) { + after ? after.after(node) : parent.append(node); + }; + }); +}; diff --git a/src/ng/directive/ngAnimate.js b/src/ng/directive/ngAnimate.js new file mode 100644 index 000000000000..d7ff79d56d21 --- /dev/null +++ b/src/ng/directive/ngAnimate.js @@ -0,0 +1,60 @@ +'use strict'; + +var ngAnimateDirective = function($animation) { + return { + priority : 9000, //this needs to always be higher than ngRepeat + controller: Animator, + require: 'ngAnimate', + link: function(scope, element, attrs, animationCntl) { + var ngAnimateAttr = attrs.ngAnimate; + + //SAVED: http://rubular.com/r/0DCBzCtVml + var matches = ngAnimateAttr.split(/(?:([-\w]+)\ *:\ *([-\w]+)(?:;|$))+/g); + if(!matches || matches.length == 0) { + throw Error("Expected ngAnimate in form of 'animation: definition; ...;' but got '" + ngAnimateAttr + "'."); + } + for(var i=1; i < matches.length; i++) { + var name = matches[i++]; + var value = matches[i++]; + if(name && value) { + var animator = $animation(value); + if(!animator || typeof(animator) != 'function') { + throw new Error("Expected '" + value + "' to be defined for the '" + name + "' animation in ngAnimate"); + } + animationCntl.set(name, animator); + } + } + } + }; +}; + +var Animator = function($animation) { + var animators = { + 'enter': $animation('noopEnter'), + 'leave': $animation('noopLeave'), + 'move': $animation('noopMove') + }; + + this.set = function(name, animator) { + if(typeof(animator) != 'function') { + throw Error("'" + name + "' does not have a properly defined animation function"); + } + animators[name] = animator; + }, + + this.animate = function(name, node, parent, after) { + var animator = animators[name]; + if(typeof(animator) != 'function') { + throw Error("'" + name + "' animator method was not found"); + } + animator(node, parent, after); + }; +}; +Animator.$inject = ['$animation']; + +var $noopAnimatorFactory = ['$animation', function($animation) { + var ctl = new Animator($animation); + + ctl.set = undefined; // prevent anyone from changing it. + return ctl; +}]; diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index c59fefacc956..7591bb067919 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -57,133 +57,137 @@ */ -var ngRepeatDirective = ngDirective({ - transclude: 'element', - priority: 1000, - terminal: true, - compile: function(element, attr, linker) { - return function(scope, iterStartElement, attr){ - var expression = attr.ngRepeat; - var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), - lhs, rhs, valueIdent, keyIdent; - if (! match) { - throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" + - expression + "'."); - } - lhs = match[1]; - rhs = match[2]; - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); - if (!match) { - throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + - lhs + "'."); - } - valueIdent = match[3] || match[1]; - keyIdent = match[2]; - - // Store a list of elements from previous run. This is a hash where key is the item from the - // iterator, and the value is an array of objects with following properties. - // - scope: bound scope - // - element: previous element. - // - index: position - // We need an array of these objects since the same object can be returned from the iterator. - // We expect this to be a rare case. - var lastOrder = new HashQueueMap(); - - scope.$watch(function ngRepeatWatch(scope){ - var index, length, - collection = scope.$eval(rhs), - cursor = iterStartElement, // current position of the node - // Same as lastOrder but it has the current state. It will become the - // lastOrder on the next iteration. - nextOrder = new HashQueueMap(), - arrayLength, - childScope, - key, value, // key/value of iteration - array, - last; // last object information {scope, element, index} - - - - if (!isArray(collection)) { - // if object, extract keys, sort them and use to determine order of iteration over obj props - array = []; - for(key in collection) { - if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { - array.push(key); +var ngRepeatDirective = ['$noopAnimator', function($noopAnimator) { + return { + transclude: 'element', + priority: 1000, + terminal: true, + require: '?ngAnimate', // optional + compile: function(element, attr, linker) { + return function(scope, iterStartElement, attr, animator){ + animator = animator || $noopAnimator; + var expression = attr.ngRepeat; + var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), + lhs, rhs, valueIdent, keyIdent; + if (! match) { + throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" + + expression + "'."); + } + lhs = match[1]; + rhs = match[2]; + match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + if (!match) { + throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + + lhs + "'."); + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; + + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is an array of objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + // We need an array of these objects since the same object can be returned from the iterator. + // We expect this to be a rare case. + var lastOrder = new HashQueueMap(); + + scope.$watch(function ngRepeatWatch(scope){ + var index, length, + collection = scope.$eval(rhs), + cursor = iterStartElement, // current position of the node + // Same as lastOrder but it has the current state. It will become the + // lastOrder on the next iteration. + nextOrder = new HashQueueMap(), + arrayLength, + childScope, + key, value, // key/value of iteration + array, + last; // last object information {scope, element, index} + + + + if (!isArray(collection)) { + // if object, extract keys, sort them and use to determine order of iteration over obj props + array = []; + for(key in collection) { + if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { + array.push(key); + } } + array.sort(); + } else { + array = collection || []; } - array.sort(); - } else { - array = collection || []; - } - arrayLength = array.length; + arrayLength = array.length; - // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0, length = array.length; index < length; index++) { - key = (collection === array) ? index : array[index]; - value = collection[key]; + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0, length = array.length; index < length; index++) { + key = (collection === array) ? index : array[index]; + value = collection[key]; - last = lastOrder.shift(value); + last = lastOrder.shift(value); - if (last) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - childScope = last.scope; - nextOrder.push(value, last); + if (last) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = last.scope; + nextOrder.push(value, last); - if (index === last.index) { - // do nothing - cursor = last.element; + if (index === last.index) { + // do nothing + cursor = last.element; + } else { + // existing item which got moved + last.index = index; + // This may be a noop, if the element is next, but I don't know of a good way to + // figure this out, since it would require extra DOM access, so let's just hope that + // the browsers realizes that it is noop, and treats it as such. + animator.animate('move', last.element, iterStartElement.parent(), cursor); + cursor = last.element; + } } else { - // existing item which got moved - last.index = index; - // This may be a noop, if the element is next, but I don't know of a good way to - // figure this out, since it would require extra DOM access, so let's just hope that - // the browsers realizes that it is noop, and treats it as such. - cursor.after(last.element); - cursor = last.element; + // new item which we don't know about + childScope = scope.$new(); } - } else { - // new item which we don't know about - childScope = scope.$new(); - } - childScope[valueIdent] = value; - if (keyIdent) childScope[keyIdent] = key; - childScope.$index = index; - - childScope.$first = (index === 0); - childScope.$last = (index === (arrayLength - 1)); - childScope.$middle = !(childScope.$first || childScope.$last); - - if (!last) { - linker(childScope, function(clone){ - cursor.after(clone); - last = { - scope: childScope, - element: (cursor = clone), - index: index - }; - nextOrder.push(value, last); - }); + childScope[valueIdent] = value; + if (keyIdent) childScope[keyIdent] = key; + childScope.$index = index; + + childScope.$first = (index === 0); + childScope.$last = (index === (arrayLength - 1)); + childScope.$middle = !(childScope.$first || childScope.$last); + + if (!last) { + linker(childScope, function(clone){ + animator.animate('enter', clone, iterStartElement.parent(), cursor); + last = { + scope: childScope, + element: (cursor = clone), + index: index + }; + nextOrder.push(value, last); + }); + } } - } - //shrink children - for (key in lastOrder) { - if (lastOrder.hasOwnProperty(key)) { - array = lastOrder[key]; - while(array.length) { - value = array.pop(); - value.element.remove(); - value.scope.$destroy(); + //shrink children + for (key in lastOrder) { + if (lastOrder.hasOwnProperty(key)) { + array = lastOrder[key]; + while(array.length) { + value = array.pop(); + animator.animate('leave', value.element, value.element.parent()); + value.scope.$destroy(); + } } } - } - lastOrder = nextOrder; - }); - }; - } -}); + lastOrder = nextOrder; + }); + }; + } + }; +}]; diff --git a/test/ng/animationControllerSpec.js b/test/ng/animationControllerSpec.js new file mode 100644 index 000000000000..8c969e51dcfb --- /dev/null +++ b/test/ng/animationControllerSpec.js @@ -0,0 +1,117 @@ +'use strict'; + +describe("Animator", function() { + + var element, animationCntl; + + beforeEach(inject(function($injector) { + animationCntl = $injector.instantiate(Animator); + })); + + afterEach(function(){ + dealoc(element); + }); + + describe("set()", function() { + it("should throw an error when used incorrectly", function() { + var fn1 = function() { + animationCntl.set('custom1'); + }; + expect(fn1).toThrow(); + + var fn2 = function() { + animationCntl.set('custom2', null); + }; + expect(fn2).toThrow(); + + var fn3 = function() { + animationCntl.set('custom3', false); + }; + expect(fn3).toThrow(); + + var fn4 = function() { + animationCntl.set('custom4', 'b'); + }; + expect(fn4).toThrow(); + + var fn5 = function() { + animationCntl.set('custom5', 0); + }; + expect(fn5).toThrow(); + + var fn6 = function() { + animationCntl.set('custom6', 100); + }; + expect(fn6).toThrow(); + + var fn7 = function() { + animationCntl.set('custom7', []); + }; + expect(fn7).toThrow(); + + var fn8 = function() { + animationCntl.set('custom8', undefined); + }; + expect(fn8).toThrow(); + }); + + it("should not throw an error when used correctly", function() { + var fn = function() { + animationCntl.set('custom', function() { }); + }; + expect(fn).not.toThrow(); + }); + + it("should overwrite the previous animator when set", function() { + var response; + animationCntl.set('enter', function() { + response = 'a'; + }); + animationCntl.set('enter', function() { + response = 'b'; + }); + animationCntl.animate('enter'); + expect(response).toBe('b'); + }); + }); + + describe("animate()", function() { + it("should be defined", function() { + expect(typeof(animationCntl.animate)).toBe('function'); + }); + + it("should throw an error when an animation is not defined", function() { + var fn = function() { + animationCntl.animate('custom'); + }; + expect(fn).toThrow(); + }); + + it("should not throw an error when an animation is defined", function() { + animationCntl.set('custom', function() {}); + var fn = function() { + animationCntl.animate('custom'); + }; + expect(fn).not.toThrow(); + }); + + it("should run the correct animator instantly", function() { + var hasRun = false; + animationCntl.set('enter', function() { + hasRun = true; + }); + animationCntl.animate('enter'); + expect(hasRun).toBe(true); + }); + + it("should run a custom animator", function() { + var hasRun = false; + animationCntl.set('custom', function() { + hasRun = true; + }); + animationCntl.animate('custom'); + expect(hasRun).toBe(true); + }); + }); + +}); diff --git a/test/ng/animationSpec.js b/test/ng/animationSpec.js new file mode 100644 index 000000000000..a6991a7d85db --- /dev/null +++ b/test/ng/animationSpec.js @@ -0,0 +1,112 @@ +'use strict'; + +describe('animations', function() { + + var animation, module, injector; + + beforeEach(inject(function($animation, $injector){ + module = angular.module('ng'); + animation = $animation; + injector = $injector; + })); + + it("should be a function that can be called", function() { + expect(typeof(animation)).toBe('function'); + }); + + describe('module', function() { + + it("should have an animation module method available", function() { + expect(typeof(module.animation)).toBe('function'); + }); + + /* + it("should define an animation", function() { + var value = {}; + animation('customAni', function() { + return value; + }); + + expect(animation('customAni')).toBe(value); + //expect(injector.get('customAniAnimation')).toBe(value); + }); + */ + + }); + + describe('$AnimationProvider', function() { + + it("should exist", function() { + expect($AnimationProvider).toBeDefined(); + }); + + /* + describe("method definitions", function() { + + var providedAnimation; + beforeEach(inject(function() { + var injector = angular.injector(); + providedAnimation = injector.invoke($AnimationProvider); + })); + + it("should have a register function", function() { + expect(typeof(providedAnimation.register)).toBe('function'); + }); + + it("should have a $get function", function() { + expect(typeof(providedAnimation.$get)).toBe('function'); + }); + }); + */ + + }); + + describe('noopAnimations', function() { + + describe("enter",function() { + var enter; + beforeEach(function() { + enter = animation('noopEnter'); + }); + + it("should have a default animation", function() { + expect(typeof(enter)).toBe('function'); + }); + + it("the animation should be defined as an injector", function() { + expect(injector.get('noopEnterAnimation')).toBe(enter); + }); + }); + + describe("leave",function() { + var leave; + beforeEach(function() { + leave = animation('noopLeave'); + }); + + it("should have a default animation", function() { + expect(typeof(leave)).toBe('function'); + }); + + it("the animation should be defined as an injector", function() { + expect(injector.get('noopLeaveAnimation')).toBe(leave); + }); + }); + + describe("move",function() { + var move; + beforeEach(function() { + move = animation('noopMove'); + }); + + it("should have a default animation", function() { + expect(typeof(move)).toBe('function'); + }); + + it("the animation should be defined as an injector", function() { + expect(injector.get('noopMoveAnimation')).toBe(move); + }); + }); + }); + +}); diff --git a/test/ng/directive/ngAnimateSpec.js b/test/ng/directive/ngAnimateSpec.js new file mode 100644 index 000000000000..ae5b2a77685f --- /dev/null +++ b/test/ng/directive/ngAnimateSpec.js @@ -0,0 +1,209 @@ +'use strict'; + +describe('ngAnimate', function() { + + var element, $compile, scope, animation; + var animationProvider; + + beforeEach(module(function($animationProvider) { + animationProvider = $animationProvider; + + return function(_$compile_, $rootScope, $animation) { + $compile = _$compile_; + scope = $rootScope.$new(); + animation = $animation; + }; + })); + beforeEach(inject()); + + afterEach(function(){ + dealoc(element); + }); + + it("should not throw an error when no defaults are provided", function() { + var fn1 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn1).not.toThrow(); + + var fn2 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn2).not.toThrow(); + }); + + it("should throw an error when any of the animators are not defined", function() { + var fn1 = function() { + element = jqLite('
'); + $compile(element)(scope); + }; + expect(fn1).toThrow(); + dealoc(element); + + var fn2 = function() { + element = jqLite('
'); + $compile(element)(scope); + }; + expect(fn2).toThrow(); + dealoc(element); + + var fn3 = function() { + element = jqLite('
'); + $compile(element)(scope); + }; + expect(fn3).toThrow(); + dealoc(element); + + var fn4 = function() { + element = jqLite('
'); + $compile(element)(scope); + }; + expect(fn4).toThrow(); + dealoc(element); + }); + + describe("custom animations", function() { + + it("should not throw an error when a custom animation is already defined", function() { + animationProvider.register('custom-animation', function() { + return function() {}; + }); + + var fn1 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn1).not.toThrow(); + + var fn2 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn2).not.toThrow(); + }); + + }); + + describe("noop animations", function() { + it("should have an enter animation defined", function() { + var fn1 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn1).not.toThrow(); + + var fn2 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn2).not.toThrow(); + }); + + it("should have a leave animation defined", function() { + var fn1 = function() { + element = jqLite('
'); + $compile(element)(scope); + dealoc(element); + }; + expect(fn1).not.toThrow(); + + var fn2 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn2).not.toThrow(); + }); + + it("should have a move animation defined", function() { + var fn1 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn1).not.toThrow(); + + var fn2 = function() { + var html = '
'; + element = $compile(html)(scope); + dealoc(element); + }; + expect(fn2).not.toThrow(); + }); + }); + + describe("ngRepeat", function() { + + describe("with noop animations", function() { + beforeEach(function() { + scope.items = [1,2,3]; + var html = '
'; + element = $compile(html)(scope); + scope.$digest(); + }); + + it("should have entered the values properly", function() { + expect(element.children().length).toBe(3); + }); + + it("should perform an enter animation properly", function() { + scope.items.push(4); + scope.$digest(); + expect(element.children().length).toBe(4); + }); + + it("should perform a leave animation properly", function() { + scope.items = [1,2]; + scope.$digest(); + expect(element.children().length).toBe(2); + }); + + it("should perform a move animation properly", function() { + scope.items = [3,2,1]; + scope.$digest(); + expect(element.children().length).toBe(3); + }); + }); + + describe("with no ngAnimate directive", function() { + beforeEach(function() { + scope.items = [1,2,3,4]; + var html = '
'; + element = $compile(html)(scope); + scope.$digest(); + }); + + it("should have entered the values properly", function() { + expect(element.children().length).toBe(4); + }); + + it("should perform an enter animation properly", function() { + scope.items.push(5); + scope.$digest(); + expect(element.children().length).toBe(5); + }); + + it("should perform a leave animation properly", function() { + scope.items = [1,2,3]; + scope.$digest(); + expect(element.children().length).toBe(3); + }); + + it("should perform a move animation properly", function() { + scope.items = [4,3,2,1]; + scope.$digest(); + expect(element.children().length).toBe(4); + }); + }); + + }); + +}); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index d55ff0159ca3..20ea336f1f25 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -56,7 +56,8 @@ afterEach(function() { }); }); if (count) { - throw new Error('Found jqCache references that were not deallocated! count: ' + count); + throw new Error(jasmine.getEnv().currentSpec.getFullName() + + ': Found jqCache references that were not deallocated! count: ' + count); } });