mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-23 01:40:25 +00:00
fix(ngAnimate): ensure that ngClass is always compiled before enter, leave and move animations
Closes #3727 Closes #3603
This commit is contained in:
parent
4e15c4fb47
commit
36ad40b18c
4 changed files with 147 additions and 37 deletions
|
|
@ -201,9 +201,9 @@ angular.module('ngAnimate', ['ng'])
|
|||
|
||||
var NG_ANIMATE_STATE = '$$ngAnimateState';
|
||||
var rootAnimateState = {running:true};
|
||||
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout',
|
||||
function($delegate, $injector, $sniffer, $rootElement, $timeout) {
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope',
|
||||
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope) {
|
||||
|
||||
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
|
||||
|
||||
function lookup(name) {
|
||||
|
|
@ -282,8 +282,10 @@ angular.module('ngAnimate', ['ng'])
|
|||
*/
|
||||
enter : function(element, parent, after, done) {
|
||||
$delegate.enter(element, parent, after);
|
||||
performAnimation('enter', 'ng-enter', element, parent, after, function() {
|
||||
done && $timeout(done, 0, false);
|
||||
$rootScope.$$postDigest(function() {
|
||||
performAnimation('enter', 'ng-enter', element, parent, after, function() {
|
||||
done && $timeout(done, 0, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -315,8 +317,10 @@ angular.module('ngAnimate', ['ng'])
|
|||
* @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);
|
||||
$rootScope.$$postDigest(function() {
|
||||
performAnimation('leave', 'ng-leave', element, null, null, function() {
|
||||
$delegate.leave(element, done);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -352,8 +356,10 @@ angular.module('ngAnimate', ['ng'])
|
|||
*/
|
||||
move : function(element, parent, after, done) {
|
||||
$delegate.move(element, parent, after);
|
||||
performAnimation('move', 'ng-move', element, null, null, function() {
|
||||
done && $timeout(done, 0, false);
|
||||
$rootScope.$$postDigest(function() {
|
||||
performAnimation('move', 'ng-move', element, null, null, function() {
|
||||
done && $timeout(done, 0, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -550,6 +556,7 @@ angular.module('ngAnimate', ['ng'])
|
|||
|
||||
var durationKey = 'Duration',
|
||||
delayKey = 'Delay',
|
||||
propertyKey = 'Property',
|
||||
animationIterationCountKey = 'IterationCount',
|
||||
ELEMENT_NODE = 1;
|
||||
|
||||
|
|
@ -610,6 +617,13 @@ angular.module('ngAnimate', ['ng'])
|
|||
timeout is empty (this would cause a flicker bug normally
|
||||
in the page */
|
||||
if(duration > 0) {
|
||||
var 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).
|
||||
node.style[w3cTransitionProp + propertyKey] = 'none';
|
||||
node.style[vendorTransitionProp + propertyKey] = 'none';
|
||||
|
||||
var activeClassName = '';
|
||||
forEach(className.split(' '), function(klass, i) {
|
||||
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
|
||||
|
|
@ -617,6 +631,8 @@ angular.module('ngAnimate', ['ng'])
|
|||
|
||||
//this triggers a reflow which allows for the transition animation to kick in
|
||||
element.prop('clientWidth');
|
||||
node.style[w3cTransitionProp + propertyKey] = '';
|
||||
node.style[vendorTransitionProp + propertyKey] = '';
|
||||
element.addClass(activeClassName);
|
||||
|
||||
$timeout(done, duration * 1000, false);
|
||||
|
|
|
|||
|
|
@ -308,40 +308,107 @@ describe('ngClass', function() {
|
|||
describe('ngClass animations', function() {
|
||||
var body, element, $rootElement;
|
||||
|
||||
beforeEach(module('mock.animate'));
|
||||
|
||||
it("should avoid calling addClass accidentally when removeClass is going on",
|
||||
it("should avoid calling addClass accidentally when removeClass is going on", function() {
|
||||
module('mock.animate');
|
||||
inject(function($compile, $rootScope, $animate, $timeout) {
|
||||
var element = angular.element('<div ng-class="val"></div>');
|
||||
var body = jqLite(document.body);
|
||||
body.append(element);
|
||||
$compile(element)($rootScope);
|
||||
|
||||
var element = angular.element('<div ng-class="val"></div>');
|
||||
var body = jqLite(document.body);
|
||||
body.append(element);
|
||||
$compile(element)($rootScope);
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
expect($animate.queue.length).toBe(0);
|
||||
$rootScope.val = 'one';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('addClass');
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.val = 'one';
|
||||
$timeout.flush();
|
||||
$rootScope.val = '';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('removeClass'); //only removeClass is called
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('addClass');
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
$rootScope.val = 'one';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.val = '';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('removeClass'); //only removeClass is called
|
||||
expect($animate.queue.length).toBe(0);
|
||||
$rootScope.val = 'two';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('removeClass');
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.val = 'one';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
it("should consider the ngClass expression evaluation before performing an animation", function() {
|
||||
|
||||
$rootScope.val = 'two';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('removeClass');
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
}));
|
||||
//mocks are not used since the enter delegation method is called before addClass and
|
||||
//it makes it impossible to test to see that addClass is called first
|
||||
module('ngAnimate');
|
||||
|
||||
var digestQueue = [];
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.register('.crazy', function() {
|
||||
return {
|
||||
enter : function(element, done) {
|
||||
element.data('state', 'crazy-enter');
|
||||
done();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return function($rootScope) {
|
||||
var before = $rootScope.$$postDigest;
|
||||
$rootScope.$$postDigest = function() {
|
||||
var args = arguments;
|
||||
digestQueue.push(function() {
|
||||
before.apply($rootScope, args);
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) {
|
||||
|
||||
//since we skip animations upon first digest, this needs to be set to true
|
||||
$animate.enabled(true);
|
||||
|
||||
$rootScope.val = 'crazy';
|
||||
var element = angular.element('<div ng-class="val"></div>');
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
|
||||
var enterComplete = false;
|
||||
$animate.enter(element, $rootElement, null, function() {
|
||||
enterComplete = true;
|
||||
});
|
||||
|
||||
//jquery doesn't compare both elements properly so let's use the nodes
|
||||
expect(element.parent()[0]).toEqual($rootElement[0]);
|
||||
expect(element.hasClass('crazy')).toBe(false);
|
||||
expect(enterComplete).toBe(false);
|
||||
|
||||
expect(digestQueue.length).toBe(1);
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
expect(element.hasClass('crazy')).toBe(true);
|
||||
expect(enterComplete).toBe(false);
|
||||
|
||||
digestQueue.shift()(); //enter
|
||||
expect(digestQueue.length).toBe(0);
|
||||
|
||||
//we don't normally need this, but since the timing between digests
|
||||
//is spaced-out then it is required so that the original digestion
|
||||
//is kicked into gear
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
|
||||
expect(element.data('state')).toBe('crazy-enter');
|
||||
expect(enterComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(element.contents().length).toBe(0);
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
|
|
@ -148,6 +149,8 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(element.contents().length).toBe(1);
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-leave')).toBe(true);
|
||||
expect(child.hasClass('ng-leave-active')).toBe(true);
|
||||
|
|
@ -169,6 +172,7 @@ describe("ngAnimate", function() {
|
|||
element.append(child2);
|
||||
expect(element.text()).toBe('12');
|
||||
$animate.move(child1, element, child2);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('21');
|
||||
}));
|
||||
|
||||
|
|
@ -213,6 +217,8 @@ describe("ngAnimate", function() {
|
|||
|
||||
//enter
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(child.attr('class')).toContain('ng-enter');
|
||||
expect(child.attr('class')).toContain('ng-enter-active');
|
||||
$timeout.flushNext(1000);
|
||||
|
|
@ -220,6 +226,8 @@ describe("ngAnimate", function() {
|
|||
//move
|
||||
element.append(after);
|
||||
$animate.move(child, element, after);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(child.attr('class')).toContain('ng-move');
|
||||
expect(child.attr('class')).toContain('ng-move-active');
|
||||
$timeout.flushNext(1000);
|
||||
|
|
@ -238,6 +246,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
//leave
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('class')).toContain('ng-leave');
|
||||
expect(child.attr('class')).toContain('ng-leave-active');
|
||||
$timeout.flushNext(1000);
|
||||
|
|
@ -274,6 +283,7 @@ describe("ngAnimate", function() {
|
|||
expect(child).toBeShown();
|
||||
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
expect(child).toBeHidden(); //hides instantly
|
||||
|
||||
//lets change this to prove that done doesn't fire anymore for the previous hide() operation
|
||||
|
|
@ -682,6 +692,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
element[0].className = 'abc';
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
|
||||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('abc ng-enter')).toBe(true);
|
||||
|
|
@ -692,6 +703,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
element[0].className = 'xyz';
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
|
||||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('xyz')).toBe(true);
|
||||
|
|
@ -717,6 +729,7 @@ describe("ngAnimate", function() {
|
|||
element.attr('class','one two');
|
||||
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
if($sniffer.transitions) {
|
||||
expect(element.hasClass('one two ng-enter')).toBe(true);
|
||||
expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true);
|
||||
|
|
@ -766,6 +779,7 @@ describe("ngAnimate", function() {
|
|||
$animate.enter(element, parent, null, function() {
|
||||
flag = true;
|
||||
});
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
|
|
@ -784,6 +798,7 @@ describe("ngAnimate", function() {
|
|||
$animate.leave(element, function() {
|
||||
flag = true;
|
||||
});
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
|
|
@ -803,6 +818,7 @@ describe("ngAnimate", function() {
|
|||
$animate.move(element, parent, parent2, function() {
|
||||
flag = true;
|
||||
});
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
|
|
@ -1212,6 +1228,7 @@ describe("ngAnimate", function() {
|
|||
var child = $compile('<div>...</div>')($rootScope);
|
||||
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
|
|
@ -1233,6 +1250,7 @@ describe("ngAnimate", function() {
|
|||
var child = $compile('<div>...</div>')($rootScope);
|
||||
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
|
|
@ -1257,6 +1275,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
}));
|
||||
|
||||
|
|
@ -1283,6 +1302,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
child.addClass('custom');
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flushNext(10);
|
||||
|
||||
|
|
@ -1315,6 +1335,7 @@ describe("ngAnimate", function() {
|
|||
var child = $compile('<div>...</div>')($rootScope);
|
||||
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
//this is added/removed right away otherwise
|
||||
if($sniffer.transitions) {
|
||||
|
|
@ -1325,6 +1346,7 @@ describe("ngAnimate", function() {
|
|||
expect(child.hasClass('this-is-mine-now')).toBe(false);
|
||||
child.addClass('usurper');
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(false);
|
||||
|
|
|
|||
|
|
@ -637,6 +637,9 @@ describe('ngView animations', function() {
|
|||
$rootScope.$digest();
|
||||
|
||||
$animate.flushNext('leave'); //ngView old
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.flushNext('enter'); //ngView new
|
||||
|
||||
expect(n(element.text())).toEqual(''); //this is midway during the animation
|
||||
|
|
@ -644,6 +647,8 @@ describe('ngView animations', function() {
|
|||
$animate.flushNext('enter'); //ngRepeat 3
|
||||
$animate.flushNext('enter'); //ngRepeat 4
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.text()).toEqual('34');
|
||||
|
||||
function n(text) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue