diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 5aed6832e619..ee637b3ba4a1 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -600,40 +600,43 @@ angular.module('ngAnimate', ['ng']) element.addClass(className); //we want all the styles defined before and after - var transitionTime = 0, - animationTime = 0; + var transitionDuration = 0, + animationDuration = 0, + transitionDelay = 0, + animationDelay = 0; forEach(element, function(element) { if (element.nodeType == ELEMENT_NODE) { var elementStyles = $window.getComputedStyle(element) || {}; - var transitionDelay = parseMaxTime(elementStyles[transitionProp + delayKey]); + transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay); - var animationDelay = parseMaxTime(elementStyles[animationProp + delayKey]); + animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay); - var transitionDuration = parseMaxTime(elementStyles[transitionProp + durationKey]); + transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration); - var animationDuration = parseMaxTime(elementStyles[animationProp + durationKey]); + var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]); - if(animationDuration > 0) { - animationDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1; + if(aDuration > 0) { + aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1; } - transitionTime = Math.max(transitionDelay + transitionDuration, transitionTime); - animationTime = Math.max(animationDelay + animationDuration, animationTime); + animationDuration = Math.max(aDuration, animationDuration); } }); /* there is no point in performing a reflow if the animation timeout is empty (this would cause a flicker bug normally - in the page */ - var maxTime = Math.max(transitionTime,animationTime) * 1000; - if(maxTime > 0) { - var node = element[0], - startTime = Date.now(); + in the page. There is also no point in performing an animation + that only has a delay and no duration */ + var maxDuration = Math.max(transitionDuration, animationDuration); + if(maxDuration > 0) { + var maxDelayTime = Math.max(transitionDelay, animationDelay) * 1000, + startTime = Date.now(), + node = element[0]; //temporarily disable the transition so that the enter styles //don't animate twice (this is here to avoid a bug in Chrome/FF). - if(transitionTime > 0) { + if(transitionDuration > 0) { node.style[transitionProp + propertyKey] = 'none'; } @@ -644,7 +647,7 @@ angular.module('ngAnimate', ['ng']) // This triggers a reflow which allows for the transition animation to kick in. element.prop('clientWidth'); - if(transitionTime > 0) { + if(transitionDuration > 0) { node.style[transitionProp + propertyKey] = ''; } element.addClass(activeClassName); @@ -678,8 +681,13 @@ angular.module('ngAnimate', ['ng']) var ev = event.originalEvent || event; /* $manualTimeStamp is a mocked timeStamp value which is set * within browserTrigger(). This is only here so that tests can - * mock animations properly. Real events fallback to event.timeStamp. */ - if((ev.$manualTimeStamp || ev.timeStamp) - startTime >= maxTime) { + * mock animations properly. Real events fallback to event.timeStamp. + * We're checking to see if the timestamp surpasses the expected delay, + * but we're using elapsedTime instead of the timestamp on the 2nd + * pre-condition since animations sometimes close off early */ + if((ev.$manualTimeStamp || ev.timeStamp) - startTime >= maxDelayTime && + ev.elapsedTime >= maxDuration) { + done(); } } diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index f5faa0241680..90868ef6abc4 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -137,7 +137,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); - browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(element.contents().length).toBe(1); @@ -153,7 +153,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-leave')).toBe(true); expect(child.hasClass('ng-leave-active')).toBe(true); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(element.contents().length).toBe(0); @@ -185,7 +185,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-hide-remove')).toBe(true); expect(child.hasClass('ng-hide-remove-active')).toBe(true); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(child.hasClass('ng-hide-remove')).toBe(false); expect(child.hasClass('ng-hide-remove-active')).toBe(false); @@ -201,7 +201,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-hide-add')).toBe(true); expect(child.hasClass('ng-hide-add-active')).toBe(true); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(child).toBeHidden(); })); @@ -220,7 +220,7 @@ describe("ngAnimate", function() { expect(child.attr('class')).toContain('ng-enter'); expect(child.attr('class')).toContain('ng-enter-active'); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); //move element.append(after); @@ -229,26 +229,26 @@ describe("ngAnimate", function() { expect(child.attr('class')).toContain('ng-move'); expect(child.attr('class')).toContain('ng-move-active'); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); //hide $animate.addClass(child, 'ng-hide'); expect(child.attr('class')).toContain('ng-hide-add'); expect(child.attr('class')).toContain('ng-hide-add-active'); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); //show $animate.removeClass(child, 'ng-hide'); expect(child.attr('class')).toContain('ng-hide-remove'); expect(child.attr('class')).toContain('ng-hide-remove-active'); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); //leave $animate.leave(child); $rootScope.$digest(); expect(child.attr('class')).toContain('ng-leave'); expect(child.attr('class')).toContain('ng-leave-active'); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); })); it("should not run if animations are disabled", @@ -291,7 +291,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(element.children().length).toBe(1); //still animating - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } $timeout.flush(2000); $timeout.flush(2000); @@ -308,7 +308,7 @@ describe("ngAnimate", function() { child.addClass('custom-delay ng-hide'); $animate.removeClass(child, 'ng-hide'); if($sniffer.transitions) { - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } $timeout.flush(2000); @@ -372,7 +372,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if($sniffer.transitions) { - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } $timeout.flush(2000); $timeout.flush(20000); @@ -414,7 +414,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.animations) { - browserTrigger(element,'animationend', { timeStamp: Date.now() + 4000 }); + browserTrigger(element,'animationend', { timeStamp: Date.now() + 4000, elapsedTime: 4000 }); } expect(element).toBeShown(); })); @@ -437,7 +437,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.animations) { - browserTrigger(element,'animationend', { timeStamp: Date.now() + 6000 }); + browserTrigger(element,'animationend', { timeStamp: Date.now() + 6000, elapsedTime: 6000 }); } expect(element).toBeShown(); })); @@ -460,7 +460,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.animations) { - browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000 }); + browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000, elapsedTime: 2000 }); } expect(element).toBeShown(); })); @@ -485,7 +485,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { - browserTrigger(element,'animationend', { timeStamp : Date.now() + 20000 }); + browserTrigger(element,'animationend', { timeStamp : Date.now() + 20000, elapsedTime: 20000 }); } expect(element).toBeShown(); })); @@ -530,7 +530,7 @@ describe("ngAnimate", function() { if($sniffer.animations) { //cleanup some pending animations expect(element.hasClass('ng-hide-add')).toBe(true); expect(element.hasClass('ng-hide-add-active')).toBe(true); - browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000 }); + browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000, elapsedTime: 2000 }); } expect(element.hasClass('ng-hide-remove-active')).toBe(false); @@ -562,7 +562,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(element).toBeShown(); })); @@ -584,9 +584,9 @@ describe("ngAnimate", function() { if ($sniffer.transitions) { expect(element).toBeHidden(); var now = Date.now(); - browserTrigger(element,'transitionend', { timeStamp: now + 1000 }); - browserTrigger(element,'transitionend', { timeStamp: now + 1000 }); - browserTrigger(element,'transitionend', { timeStamp: now + 2000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2000 }); } expect(element).toBeShown(); })); @@ -618,9 +618,9 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { var now = Date.now(); - browserTrigger(element,'transitionend', { timeStamp: now + 1000 }); - browserTrigger(element,'transitionend', { timeStamp: now + 3000 }); - browserTrigger(element,'transitionend', { timeStamp: now + 3000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 3000, elapsedTime: 3000 }); + browserTrigger(element,'transitionend', { timeStamp: now + 3000, elapsedTime: 3000 }); } expect(element).toBeShown(); })); @@ -642,7 +642,7 @@ describe("ngAnimate", function() { $animate.removeClass(element, 'ng-hide'); if ($sniffer.transitions) { - browserTrigger(element,'animationend', { timeStamp: Date.now() + 11000 }); + browserTrigger(element,'animationend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 }); } expect(element).toBeShown(); })); @@ -662,7 +662,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(element.hasClass('ng-hide-remove')).toBe(true); expect(element.hasClass('ng-hide-remove-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(element.hasClass('ng-hide-remove')).toBe(false); expect(element.hasClass('ng-hide-remove-active')).toBe(false); @@ -698,7 +698,7 @@ describe("ngAnimate", function() { if ($sniffer.transitions) { expect(element.hasClass('abc ng-enter')).toBe(true); expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22000 }); } expect(element.hasClass('abc')).toBe(true); @@ -709,7 +709,7 @@ describe("ngAnimate", function() { if ($sniffer.transitions) { expect(element.hasClass('xyz')).toBe(true); expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 }); } expect(element.hasClass('xyz')).toBe(true); })); @@ -736,7 +736,7 @@ describe("ngAnimate", function() { 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); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3000 }); } expect(element.hasClass('one two')).toBe(true); @@ -880,7 +880,7 @@ describe("ngAnimate", function() { }); if($sniffer.transitions) { - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } $timeout.flush(); expect(flag).toBe(true); @@ -1013,7 +1013,7 @@ describe("ngAnimate", function() { expect(element.hasClass('klass')).toBe(false); expect(element.hasClass('klass-add')).toBe(true); expect(element.hasClass('klass-add-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3000 }); } //this cancels out the older animation @@ -1027,7 +1027,7 @@ describe("ngAnimate", function() { expect(element.hasClass('klass-add-active')).toBe(false); expect(element.hasClass('klass-remove')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3000 }); } $timeout.flush(); @@ -1085,7 +1085,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(element.hasClass('klass-add')).toBe(true); expect(element.hasClass('klass-add-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 }); expect(element.hasClass('klass-add')).toBe(false); expect(element.hasClass('klass-add-active')).toBe(false); } @@ -1099,7 +1099,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(element.hasClass('klass-remove')).toBe(true); expect(element.hasClass('klass-remove-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11000 }); expect(element.hasClass('klass-remove')).toBe(false); expect(element.hasClass('klass-remove-active')).toBe(false); } @@ -1134,7 +1134,7 @@ describe("ngAnimate", function() { expect(element.hasClass('one-add-active')).toBe(true); expect(element.hasClass('two-add-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 7000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 7000, elapsedTime: 7000 }); expect(element.hasClass('one-add')).toBe(false); expect(element.hasClass('one-add-active')).toBe(false); @@ -1178,7 +1178,7 @@ describe("ngAnimate", function() { expect(element.hasClass('one-remove-active')).toBe(true); expect(element.hasClass('two-remove-active')).toBe(true); - browserTrigger(element,'transitionend', { timeStamp: Date.now() + 9000 }); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 9000, elapsedTime: 9000 }); expect(element.hasClass('one-remove')).toBe(false); expect(element.hasClass('one-remove-active')).toBe(false); @@ -1227,7 +1227,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(child.hasClass('ng-enter')).toBe(false); @@ -1249,7 +1249,7 @@ describe("ngAnimate", function() { if($sniffer.transitions) { expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 9000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 9000, elapsedTime: 9000 }); } expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); @@ -1275,7 +1275,7 @@ describe("ngAnimate", function() { $animate.enter(child, element); $rootScope.$digest(); - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 2000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 2000, elapsedTime: 2000 }); expect(child.css(propertyKey)).toBe('background-color'); child.remove(); @@ -1333,7 +1333,7 @@ describe("ngAnimate", function() { $timeout.flush(10); if($sniffer.transitions) { - browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1000 }); } expect(child.hasClass('i-was-animated')).toBe(true); @@ -1561,12 +1561,12 @@ describe("ngAnimate", function() { expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); - browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000 }); + browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0 }); expect(child.hasClass('ng-enter')).toBe(true); expect(child.hasClass('ng-enter-active')).toBe(true); - browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 2000 }); + browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 2000, elapsedTime: 2000 }); expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false);