diff --git a/angularFiles.js b/angularFiles.js index 6be3f249bb2f..cd8091ab4cc7 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -101,6 +101,7 @@ var angularFiles = { 'src/ngAnimate/animateJs.js', 'src/ngAnimate/animateJsDriver.js', 'src/ngAnimate/animateQueue.js', + 'src/ngAnimate/animateCache.js', 'src/ngAnimate/animation.js', 'src/ngAnimate/ngAnimateSwap.js', 'src/ngAnimate/module.js' diff --git a/src/ngAnimate/animateCache.js b/src/ngAnimate/animateCache.js new file mode 100644 index 000000000000..a2a768df117b --- /dev/null +++ b/src/ngAnimate/animateCache.js @@ -0,0 +1,55 @@ +'use strict'; + +var $$AnimateCacheProvider = function() { + var KEY = "$$ngAnimateParentKey"; + var parentCounter = 0; + var cache = Object.create(null); + + this.$get = [function() { + return { + cacheKey: function(node, method, addClass, removeClass) { + var parentNode = node.parentNode; + var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); + var parts = [parentID, method, node.getAttribute('class')]; + if (addClass) { + parts.push(addClass); + } + if (removeClass) { + parts.push(removeClass); + } + return parts.join('-'); + }, + + containsCachedValidAnimation: function(key) { + var entry = cache[key]; + + // nothing cached, so go ahead and animate + // otherwise it should be a valid animation + return (!entry || entry.isValid) ? true : false; + }, + + flush: function() { + cache = Object.create(null); + }, + + count: function(key) { + var entry = cache[key]; + return entry ? entry.total : 0; + }, + + get: function(key) { + var entry = cache[key]; + return entry && entry.value; + }, + + put: function(key, value, isValid) { + if (!cache[key]) { + cache[key] = { total: 1, value: value, isValid: isValid }; + } else { + cache[key].total++; + cache[key].value = value; + } + } + }; + }]; +}; diff --git a/src/ngAnimate/animateCss.js b/src/ngAnimate/animateCss.js index 54bc23c97922..c0a287c9e376 100644 --- a/src/ngAnimate/animateCss.js +++ b/src/ngAnimate/animateCss.js @@ -303,33 +303,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) { return [style, value]; } -function createLocalCacheLookup() { - var cache = Object.create(null); - return { - flush: function() { - cache = Object.create(null); - }, - - count: function(key) { - var entry = cache[key]; - return entry ? entry.total : 0; - }, - - get: function(key) { - var entry = cache[key]; - return entry && entry.value; - }, - - put: function(key, value) { - if (!cache[key]) { - cache[key] = { total: 1, value: value }; - } else { - cache[key].total++; - } - } - }; -} - // we do not reassign an already present style value since // if we detect the style property value again we may be // detecting styles that were added via the `from` styles. @@ -348,26 +321,15 @@ function registerRestorableStyles(backup, node, properties) { } var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { - var gcsLookup = createLocalCacheLookup(); - var gcsStaggerLookup = createLocalCacheLookup(); - - this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', + this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$animateCache', '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', - function($window, $$jqLite, $$AnimateRunner, $timeout, + function($window, $$jqLite, $$AnimateRunner, $timeout, $$animateCache, $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); - var parentCounter = 0; - function gcsHashFn(node, extraClasses) { - var KEY = "$$ngAnimateParentKey"; - var parentNode = node.parentNode; - var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); - return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; - } - - function computeCachedCssStyles(node, className, cacheKey, properties) { - var timings = gcsLookup.get(cacheKey); + function computeCachedCssStyles(node, className, cacheKey, allowInvalid, properties) { + var timings = $$animateCache.get(cacheKey); if (!timings) { timings = computeCssStyles($window, node, properties); @@ -376,20 +338,26 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { } } + // if an GCS call doesn't return anything valid for the animation then we + // should mark that so that repeated classAdd/removeRemove calls are skipped + var isValid = allowInvalid || (timings.transitionDuration > 0 || timings.animationDuration > 0); + // we keep putting this in multiple times even though the value and the cacheKey are the same // because we're keeping an internal tally of how many duplicate animations are detected. - gcsLookup.put(cacheKey, timings); + $$animateCache.put(cacheKey, timings, isValid); + return timings; } function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { var stagger; + var staggerCacheKey = 'stagger-' + cacheKey; // if we have one or more existing matches of matching elements // containing the same parent + CSS styles (which is how cacheKey works) // then staggering is possible - if (gcsLookup.count(cacheKey) > 0) { - stagger = gcsStaggerLookup.get(cacheKey); + if ($$animateCache.count(cacheKey) > 0) { + stagger = $$animateCache.get(staggerCacheKey); if (!stagger) { var staggerClassName = pendClasses(className, '-stagger'); @@ -404,7 +372,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { $$jqLite.removeClass(node, staggerClassName); - gcsStaggerLookup.put(cacheKey, stagger); + $$animateCache.put(staggerCacheKey, stagger, true); } } @@ -416,8 +384,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { function waitUntilQuiet(callback) { rafWaitQueue.push(callback); $$rAFScheduler.waitUntilQuiet(function() { - gcsLookup.flush(); - gcsStaggerLookup.flush(); + $$animateCache.flush(); // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. // PLEASE EXAMINE THE `$$forceReflow` service to understand why. @@ -432,8 +399,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { }); } - function computeTimings(node, className, cacheKey) { - var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); + function computeTimings(node, className, cacheKey, allowInvalid) { + var timings = computeCachedCssStyles(node, className, cacheKey, allowInvalid, DETECT_CSS_PROPERTIES); var aD = timings.animationDelay; var tD = timings.transitionDelay; timings.maxDelay = aD && tD @@ -520,7 +487,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); var fullClassName = classes + ' ' + preparationClasses; - var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); var hasToStyles = styles.to && Object.keys(styles.to).length > 0; var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; @@ -533,7 +499,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { return closeAndReturnNoopAnimator(); } - var cacheKey, stagger; + var stagger, cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass); + if (!$$animateCache.containsCachedValidAnimation(cacheKey)) { + preparationClasses = null; + return closeAndReturnNoopAnimator(); + } + if (options.stagger > 0) { var staggerVal = parseFloat(options.stagger); stagger = { @@ -543,7 +514,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { animationDuration: 0 }; } else { - cacheKey = gcsHashFn(node, fullClassName); stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); } @@ -577,7 +547,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { var itemIndex = stagger ? options.staggerIndex >= 0 ? options.staggerIndex - : gcsLookup.count(cacheKey) + : $$animateCache.count(cacheKey) : 0; var isFirst = itemIndex === 0; @@ -592,7 +562,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); } - var timings = computeTimings(node, fullClassName, cacheKey); + var timings = computeTimings(node, fullClassName, cacheKey, !isStructural); var relativeDelay = timings.maxDelay; maxDelay = Math.max(relativeDelay, 0); maxDuration = timings.maxDuration; @@ -630,6 +600,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { return closeAndReturnNoopAnimator(); } + var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); + if (options.delay != null) { var delayStyle; if (typeof options.delay !== "boolean") { @@ -717,10 +689,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { animationClosed = true; animationPaused = false; - if (!options.$$skipPreparationClasses) { + if (preparationClasses && !options.$$skipPreparationClasses) { $$jqLite.removeClass(element, preparationClasses); } - $$jqLite.removeClass(element, activeClasses); + + if (activeClasses) { + $$jqLite.removeClass(element, activeClasses); + } blockKeyframeAnimations(node, false); blockTransitions(node, false); @@ -893,9 +868,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { if (flags.recalculateTimingStyles) { fullClassName = node.className + ' ' + preparationClasses; - cacheKey = gcsHashFn(node, fullClassName); + cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass); - timings = computeTimings(node, fullClassName, cacheKey); + timings = computeTimings(node, fullClassName, cacheKey, false); relativeDelay = timings.maxDelay; maxDelay = Math.max(relativeDelay, 0); maxDuration = timings.maxDuration; diff --git a/src/ngAnimate/animation.js b/src/ngAnimate/animation.js index f8185a840c3d..c68d464ff4e1 100644 --- a/src/ngAnimate/animation.js +++ b/src/ngAnimate/animation.js @@ -6,6 +6,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { var drivers = this.drivers = []; var RUNNER_STORAGE_KEY = '$$animationRunner'; + var PREPARE_CLASSES_KEY = '$$animatePrepareClasses'; function setRunner(element, runner) { element.data(RUNNER_STORAGE_KEY, runner); @@ -19,8 +20,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { return element.data(RUNNER_STORAGE_KEY); } - this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', - function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { + this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', '$$animateCache', + function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler, $$animateCache) { var animationQueue = []; var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); @@ -35,6 +36,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { var animation = animations[i]; lookup.put(animation.domNode, animations[i] = { domNode: animation.domNode, + element: animation.element, fn: animation.fn, children: [] }); @@ -91,7 +93,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { result.push(row); row = []; } - row.push(entry.fn); + row.push(entry); entry.children.forEach(function(childEntry) { nextLevelEntries++; queue.push(childEntry); @@ -109,6 +111,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { // TODO(matsko): document the signature in a better way return function(element, event, options) { + var node = getDomNode(element); + options = prepareAnimationOptions(options); var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; @@ -126,8 +130,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { return runner; } - setRunner(element, runner); - var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); var tempClasses = options.tempClasses; if (tempClasses) { @@ -135,15 +137,16 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { options.tempClasses = null; } - var prepareClassName; if (isStructural) { - prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; - $$jqLite.addClass(element, prepareClassName); + element.data(PREPARE_CLASSES_KEY, 'ng-' + event + PREPARE_CLASS_SUFFIX); } + setRunner(element, runner); + animationQueue.push({ // this data is used by the postDigest code and passed into // the driver step function + cacheKey: $$animateCache.cacheKey(node, event, options.addClass, options.removeClass), element: element, classes: classes, event: event, @@ -180,16 +183,26 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { var toBeSortedAnimations = []; forEach(groupedAnimations, function(animationEntry) { + var element = animationEntry.from ? animationEntry.from.element : animationEntry.element; toBeSortedAnimations.push({ - domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), + element: element, + domNode: getDomNode(element), fn: function triggerAnimationStart() { + var startAnimationFn, closeFn = animationEntry.close; + + // in the event that we've cached the animation status for this element + // and it's infact an invalid animation (something that has duration = 0) + // then we should skip all the heavy work from here on + if (!$$animateCache.containsCachedValidAnimation(animationEntry.cacheKey)) { + closeFn(); + return; + } + // it's important that we apply the `ng-animate` CSS class and the // temporary classes before we do any driver invoking since these // CSS classes may be required for proper CSS detection. animationEntry.beforeStart(); - var startAnimationFn, closeFn = animationEntry.close; - // in the event that the element was removed before the digest runs or // during the RAF sequencing then we should not trigger the animation. var targetElement = animationEntry.anchors @@ -219,7 +232,32 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { // we need to sort each of the animations in order of parent to child // relationships. This ensures that the child classes are applied at the // right time. - $$rAFScheduler(sortAnimations(toBeSortedAnimations)); + var finalAnimations = sortAnimations(toBeSortedAnimations); + for (var i = 0; i < finalAnimations.length; i++) { + var innerArray = finalAnimations[i]; + for (var j = 0; j < innerArray.length; j++) { + var entry = innerArray[j]; + var element = entry.element; + + // the RAFScheduler code only uses functions + finalAnimations[i][j] = entry.fn; + + // the first row of elements shouldn't have a prepare-class added to them + // since the elements are at the top of the animation hierarcy and they + // will be applied without a RAF having to pass... + if (i === 0) { + element.removeData(PREPARE_CLASSES_KEY); + continue; + } + + var prepareClassName = element.data(PREPARE_CLASSES_KEY); + if (prepareClassName) { + $$jqLite.addClass(element, prepareClassName); + } + } + } + + $$rAFScheduler(finalAnimations); }); return runner; @@ -359,10 +397,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { } function beforeStart() { - element.addClass(NG_ANIMATE_CLASSNAME); - if (tempClasses) { - $$jqLite.addClass(element, tempClasses); - } + tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME; + $$jqLite.addClass(element, tempClasses); + + var prepareClassName = element.data(PREPARE_CLASSES_KEY); if (prepareClassName) { $$jqLite.removeClass(element, prepareClassName); prepareClassName = null; @@ -401,7 +439,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { $$jqLite.removeClass(element, tempClasses); } - element.removeClass(NG_ANIMATE_CLASSNAME); runner.complete(!rejected); } }; diff --git a/src/ngAnimate/module.js b/src/ngAnimate/module.js index 7b0e50c8a418..3a99c407fe7d 100644 --- a/src/ngAnimate/module.js +++ b/src/ngAnimate/module.js @@ -7,6 +7,7 @@ $$rAFSchedulerFactory, $$AnimateChildrenDirective, $$AnimateQueueProvider, + $$AnimateCacheProvider, $$AnimationProvider, $AnimateCssProvider, $$AnimateCssDriverProvider, @@ -747,6 +748,7 @@ angular.module('ngAnimate', []) .factory('$$rAFScheduler', $$rAFSchedulerFactory) .provider('$$animateQueue', $$AnimateQueueProvider) + .provider('$$animateCache', $$AnimateCacheProvider) .provider('$$animation', $$AnimationProvider) .provider('$animateCss', $AnimateCssProvider) diff --git a/test/ngAnimate/.jshintrc b/test/ngAnimate/.jshintrc index d202a2b3bf1c..3e69fdda557c 100644 --- a/test/ngAnimate/.jshintrc +++ b/test/ngAnimate/.jshintrc @@ -3,6 +3,7 @@ "browser": true, "newcap": false, "globals": { + "getDomNode": false, "mergeAnimationDetails": false, "prepareAnimationOptions": false, "applyAnimationStyles": false, diff --git a/test/ngAnimate/animateCacheSpec.js b/test/ngAnimate/animateCacheSpec.js new file mode 100644 index 000000000000..87c33d0043b1 --- /dev/null +++ b/test/ngAnimate/animateCacheSpec.js @@ -0,0 +1,96 @@ +'use strict'; + +describe("ngAnimate $$animateCache", function() { + beforeEach(module('ngAnimate')); + + it('should store the details in a lookup', inject(function($$animateCache) { + var data = { 'hello': 'there' }; + $$animateCache.put('key', data, true); + expect($$animateCache.get('key')).toBe(data); + })); + + it('should update existing stored details in a lookup', inject(function($$animateCache) { + var data = { 'hello': 'there' }; + $$animateCache.put('key', data, true); + + var otherData = { 'hi': 'you' }; + $$animateCache.put('key', otherData, true); + expect($$animateCache.get('key')).toBe(otherData); + })); + + it('should create a special cacheKey based on the element/parent and className relationship', inject(function($$animateCache) { + var cacheKey, elm = jqLite('
'); + elm.addClass('one two'); + + var parent1 = jqLite(''); + parent1.append(elm); + + cacheKey = $$animateCache.cacheKey(getDomNode(elm), 'event'); + expect(cacheKey).toBe('1-event-one two'); + + cacheKey = $$animateCache.cacheKey(getDomNode(elm), 'event', 'add'); + expect(cacheKey).toBe('1-event-one two-add'); + + cacheKey = $$animateCache.cacheKey(getDomNode(elm), 'event', 'add', 'remove'); + expect(cacheKey).toBe('1-event-one two-add-remove'); + + var parent2 = jqLite(''); + parent2.append(elm); + + cacheKey = $$animateCache.cacheKey(getDomNode(elm), 'event'); + expect(cacheKey).toBe('2-event-one two'); + + cacheKey = $$animateCache.cacheKey(getDomNode(elm), 'event', 'three', 'four'); + expect(cacheKey).toBe('2-event-one two-three-four'); + })); + + it('should keep a count of how many times a cache key has been updated', inject(function($$animateCache) { + var data = { 'hello': 'there' }; + var key = 'key'; + expect($$animateCache.count(key)).toBe(0); + + $$animateCache.put(key, data, true); + expect($$animateCache.count(key)).toBe(1); + + var otherData = { 'other': 'data' }; + $$animateCache.put(key, otherData, true); + expect($$animateCache.count(key)).toBe(2); + })); + + it('should flush the cache and the counters', inject(function($$animateCache) { + $$animateCache.put('key1', { data: 'value' }, true); + $$animateCache.put('key2', { data: 'value' }, true); + + expect($$animateCache.count('key1')).toBe(1); + expect($$animateCache.count('key2')).toBe(1); + + $$animateCache.flush(); + + expect($$animateCache.get('key1')).toBeFalsy(); + expect($$animateCache.get('key2')).toBeFalsy(); + + expect($$animateCache.count('key1')).toBe(0); + expect($$animateCache.count('key2')).toBe(0); + })); + + it('should store whether the stored entry is invalid or not', inject(function($$animateCache) { + var validEntry = { someEssentialProperty: true }; + var invalidEntry = { someEssentialProperty: false }; + + $$animateCache.put('key1', validEntry, true); + $$animateCache.put('key2', invalidEntry, false); + + expect($$animateCache.containsCachedValidAnimation('key1')).toBe(true); + expect($$animateCache.containsCachedValidAnimation('key2')).toBe(false); + })); + + it('should consider a cached entry valid if it does not exist in the cache', inject(function($$animateCache) { + expect($$animateCache.containsCachedValidAnimation('key2')).toBe(true); + + $$animateCache.put('key2', {}, false); + expect($$animateCache.containsCachedValidAnimation('key2')).toBe(false); + + $$animateCache.flush(); + expect($$animateCache.containsCachedValidAnimation('key2')).toBe(true); + })); +}); diff --git a/test/ngAnimate/animationSpec.js b/test/ngAnimate/animationSpec.js index dcce9c1219f7..8828ec02df60 100644 --- a/test/ngAnimate/animationSpec.js +++ b/test/ngAnimate/animationSpec.js @@ -36,6 +36,9 @@ describe('$$animation', function() { }); inject(function($$animation, $animate, $rootScope) { element = jqLite(''); + var parent = jqLite(''); + parent.append(element); + var done = false; $$animation(element, 'someEvent').then(function() { done = true; @@ -197,7 +200,11 @@ describe('$$animation', function() { }); inject(function($$animation, $rootScope, $animate) { - var status, element = jqLite(''); + var status; + var element = jqLite(''); + var parent = jqLite(''); + parent.append(element); + var runner = $$animation(element, 'enter'); runner.then(function() { status = 'resolve'; @@ -515,11 +522,24 @@ describe('$$animation', function() { })); - they('should add the preparation class before the $prop-animation is pushed to the queue', + they('should only apply the ng-$prop-prepare class if they are a child animation', ['enter', 'leave', 'move'], function(animationType) { inject(function($$animation, $rootScope, $animate) { - var runner = $$animation(element, animationType); - expect(element).toHaveClass('ng-' + animationType + '-prepare'); + var expectedClassName = 'ng-' + animationType + '-prepare'; + + $$animation(element, animationType); + $rootScope.$digest(); + expect(element).not.toHaveClass(expectedClassName); + + var child = jqLite(''); + element.append(child); + + $$animation(element, animationType); + $$animation(child, animationType); + $rootScope.$digest(); + + expect(element).not.toHaveClass(expectedClassName); + expect(child).toHaveClass(expectedClassName); }); }); @@ -527,9 +547,22 @@ describe('$$animation', function() { they('should remove the preparation class before the $prop-animation starts', ['enter', 'leave', 'move'], function(animationType) { inject(function($$animation, $rootScope, $$rAF) { - var runner = $$animation(element, animationType); + var expectedClassName = 'ng-' + animationType + '-prepare'; + + var child = jqLite(''); + element.append(child); + + $$animation(element, animationType); + $$animation(child, animationType); $rootScope.$digest(); - expect(element).not.toHaveClass('ng-' + animationType + '-prepare'); + + expect(element).not.toHaveClass(expectedClassName); + expect(child).toHaveClass(expectedClassName); + + $$rAF.flush(); + + expect(element).not.toHaveClass(expectedClassName); + expect(child).not.toHaveClass(expectedClassName); }); }); }); @@ -977,11 +1010,12 @@ describe('$$animation', function() { }); inject(function($$animation, $rootScope, $animate) { element.addClass('four'); + parent.append(element); var completed = false; $$animation(element, 'event', { - from: { background: 'red' }, - to: { background: 'blue', 'font-size': '50px' } + from: { height: '100px' }, + to: { height: '200px', 'font-size': '50px' } }).then(function() { completed = true; }); @@ -992,7 +1026,7 @@ describe('$$animation', function() { $rootScope.$digest(); //the runner promise expect(completed).toBe(true); - expect(element.css('background')).toContain('blue'); + expect(element.css('height')).toContain('200px'); expect(element.css('font-size')).toBe('50px'); }); }); @@ -1028,6 +1062,7 @@ describe('$$animation', function() { }); }); inject(function($$animation, $rootScope, $animate) { + parent.append(element); element.addClass('four'); var completed = false; diff --git a/test/ngAnimate/integrationSpec.js b/test/ngAnimate/integrationSpec.js index 3fee45965b50..e48eb773c09d 100644 --- a/test/ngAnimate/integrationSpec.js +++ b/test/ngAnimate/integrationSpec.js @@ -271,6 +271,8 @@ describe('ngAnimate integration tests', function() { it('should issue a RAF for each element animation on all DOM levels', function() { module('ngAnimateMock'); inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) { + ss.addRule('.ng-enter', 'transition:2s linear all;'); + element = jqLite( '