Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngAnimate): do not alter the provided options data #13175

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/ng/animateCss.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ var $CoreAnimateCssProvider = function() {
}
};

return function(element, options) {
return function(element, initialOptions) {
// we always make a copy of the options since
// there should never be any side effects on
// the input data when running `$animateCss`.
var options = copy(initialOptions);

// there is no point in applying the styles since
// there is no animation that goes on at all in
// this version of $animateCss.
Expand Down
1 change: 1 addition & 0 deletions src/ngAnimate/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"angular": false,
"noop": false,

"copy": false,
"forEach": false,
"extend": false,
"jqLite": false,
Expand Down
7 changes: 6 additions & 1 deletion src/ngAnimate/animateCss.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return timings;
}

return function init(element, options) {
return function init(element, initialOptions) {
// we always make a copy of the options since
// there should never be any side effects on
// the input data when running `$animateCss`.
var options = copy(initialOptions);

var restoreStyles = {};
var node = getDomNode(element);
if (!node
Expand Down
7 changes: 6 additions & 1 deletion src/ngAnimate/animateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
};

function queueAnimation(element, event, options) {
function queueAnimation(element, event, initialOptions) {
// we always make a copy of the options since
// there should never be any side effects on
// the input data when running `$animateCss`.
var options = copy(initialOptions);

var node, parent;
element = stripCommentsFromElement(element);
if (element) {
Expand Down
1 change: 1 addition & 0 deletions src/ngAnimate/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

/* jshint ignore:start */
var noop = angular.noop;
var copy = angular.copy;
var extend = angular.extend;
var jqLite = angular.element;
var forEach = angular.forEach;
Expand Down
15 changes: 15 additions & 0 deletions test/ng/animateCssSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ describe("$animateCss", function() {

describe("without animation", function() {

it("should not alter the provided options input in any way", inject(function($animateCss) {
var initialOptions = {
from: { height: '50px' },
to: { width: '50px' },
addClass: 'one',
removeClass: 'two'
};

var copiedOptions = copy(initialOptions);

expect(copiedOptions).toEqual(initialOptions);
$animateCss(element, copiedOptions).start();
expect(copiedOptions).toEqual(initialOptions);
}));

it("should apply the provided [from] CSS to the element", inject(function($animateCss) {
$animateCss(element, { from: { height: '50px' }}).start();
expect(element.css('height')).toBe('50px');
Expand Down
21 changes: 21 additions & 0 deletions test/ng/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,27 @@ describe("$animate", function() {
});
});

it("should not alter the provided options input in any way throughout the animation", inject(function($animate, $rootElement, $rootScope) {
var element = jqLite('<div></div>');
var parent = $rootElement;

var initialOptions = {
from: { height: '50px' },
to: { width: '50px' },
addClass: 'one',
removeClass: 'two'
};

var copiedOptions = copy(initialOptions);
expect(copiedOptions).toEqual(initialOptions);

var runner = $animate.enter(element, parent, null, copiedOptions);
expect(copiedOptions).toEqual(initialOptions);

$rootScope.$digest();
expect(copiedOptions).toEqual(initialOptions);
}));

describe('CSS class DOM manipulation', function() {
var element;
var addClass;
Expand Down
31 changes: 31 additions & 0 deletions test/ngAnimate/animateCssSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,37 @@ describe("ngAnimate $animateCss", function() {
};
}));

it("should not alter the provided options input in any way throughout the animation", inject(function($animateCss) {
var initialOptions = {
from: { height: '50px' },
to: { width: '50px' },
addClass: 'one',
removeClass: 'two',
duration: 10,
delay: 10,
structural: true,
keyframeStyle: '1s rotate',
transitionStyle: '1s linear',
stagger: 0.5,
staggerIndex: 3
};

var copiedOptions = copy(initialOptions);
expect(copiedOptions).toEqual(initialOptions);

var animator = $animateCss(element, copiedOptions);
expect(copiedOptions).toEqual(initialOptions);

var runner = animator.start();
expect(copiedOptions).toEqual(initialOptions);

triggerAnimationStartFrame();
expect(copiedOptions).toEqual(initialOptions);

runner.end();
expect(copiedOptions).toEqual(initialOptions);
}));

describe("[$$skipPreparationClasses]", function() {
it('should not apply and remove the preparation classes to the element when true',
inject(function($animateCss) {
Expand Down
34 changes: 30 additions & 4 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ describe("animations", function() {
};
}));

it("should not alter the provided options input in any way throughout the animation", inject(function($animate, $rootElement, $rootScope) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$rootElement is not needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you remove the patch, this is the only test that fails. Shouldn't all tests fail if the patch is removed?

var initialOptions = {
from: { height: '50px' },
to: { width: '50px' },
addClass: 'one',
removeClass: 'two'
};

var copiedOptions = copy(initialOptions);
expect(copiedOptions).toEqual(initialOptions);

var runner = $animate.enter(element, parent, null, copiedOptions);
expect(copiedOptions).toEqual(initialOptions);

$rootScope.$digest();
expect(copiedOptions).toEqual(initialOptions);
}));

it('should animate only the specified CSS className matched within $animateProvider.classNameFilter', function() {
module(function($animateProvider) {
$animateProvider.classNameFilter(/only-allow-this-animation/);
Expand All @@ -149,32 +167,40 @@ describe("animations", function() {
});
});

they('should nullify both options.$prop when passed into an animation if it is not a string or an array', ['addClass', 'removeClass'], function(prop) {
they('should not apply the provided options.$prop value unless it\'s a string or string-based array', ['addClass', 'removeClass'], function(prop) {
inject(function($animate, $rootScope) {
var startingCssClasses = element.attr('class') || '';

var options1 = {};
options1[prop] = function() {};
$animate.enter(element, parent, null, options1);

expect(options1[prop]).toBeFalsy();
expect(element.attr('class')).toEqual(startingCssClasses);

$rootScope.$digest();

var options2 = {};
options2[prop] = true;
$animate.leave(element, options2);

expect(options2[prop]).toBeFalsy();
expect(element.attr('class')).toEqual(startingCssClasses);

$rootScope.$digest();

capturedAnimation = null;

var options3 = {};
if (prop === 'removeClass') {
element.addClass('fatias');
startingCssClasses = element.attr('class');
}

options3[prop] = ['fatias'];
$animate.enter(element, parent, null, options3);
expect(options3[prop]).toBe('fatias');

$rootScope.$digest();

expect(element.attr('class')).not.toEqual(startingCssClasses);
});
});

Expand Down
45 changes: 45 additions & 0 deletions test/ngAnimate/integrationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -562,5 +562,50 @@ describe('ngAnimate integration tests', function() {
}
});
});

it("should not alter the provided options values in anyway throughout the animation", function() {
var animationSpy = jasmine.createSpy();
module(function($animateProvider) {
$animateProvider.register('.this-animation', function() {
return {
enter: function(element, done) {
animationSpy();
done();
}
};
});
});

inject(function($animate, $rootScope, $compile) {
element = jqLite('<div class="parent-man"></div>');
var child = jqLite('<div class="child-man one"></div>');

var initialOptions = {
from: { height: '50px' },
to: { width: '100px' },
addClass: 'one',
removeClass: 'two'
};

var copiedOptions = copy(initialOptions);
expect(copiedOptions).toEqual(initialOptions);

html(element);
$compile(element)($rootScope);

$animate.enter(child, element, null, copiedOptions);
$rootScope.$digest();
expect(copiedOptions).toEqual(initialOptions);

$animate.flush();
expect(copiedOptions).toEqual(initialOptions);

expect(child).toHaveClass('one');
expect(child).not.toHaveClass('two');

expect(child.attr('style')).toContain('100px');
expect(child.attr('style')).toContain('50px');
});
});
});
});