diff --git a/docs/app/src/examples.js b/docs/app/src/examples.js
index 5db8bf4e88f7..1f0f2aa4e83e 100644
--- a/docs/app/src/examples.js
+++ b/docs/app/src/examples.js
@@ -24,7 +24,26 @@ angular.module('examples', [])
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
- return function(exampleFolder, clickEvent) {
+
+ var COPYRIGHT = 'Copyright ' + (new Date()).getFullYear() + ' Google Inc. All Rights Reserved.\n'
+ + 'Use of this source code is governed by an MIT-style license that\n'
+ + 'can be found in the LICENSE file at http://angular.io/license';
+ var COPYRIGHT_JS_CSS = '\n\n/*\n' + COPYRIGHT + '\n*/';
+ var COPYRIGHT_HTML = '\n\n';
+ function getCopyright(filename) {
+ switch (filename.substr(filename.lastIndexOf('.'))) {
+ case '.html':
+ return COPYRIGHT_HTML;
+ case '.js':
+ case '.css':
+ return COPYRIGHT_JS_CSS;
+ case '.md':
+ return COPYRIGHT;
+ }
+ return '';
+ }
+
+ return function(exampleFolder, clickEvent) {
var exampleName = 'AngularJS Example';
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
@@ -67,7 +86,7 @@ angular.module('examples', [])
var postData = {};
angular.forEach(files, function(file) {
- postData['files[' + file.name + ']'] = file.content;
+ postData['files[' + file.name + ']'] = file.content + getCopyright(file.name);
});
postData['tags[0]'] = "angularjs";
diff --git a/src/ng/animateCss.js b/src/ng/animateCss.js
index 552e51095ebf..2e133167a6c3 100644
--- a/src/ng/animateCss.js
+++ b/src/ng/animateCss.js
@@ -15,10 +15,14 @@ var $CoreAnimateCssProvider = function() {
this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
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);
+ // all of the animation functions should create
+ // a copy of the options data, however, if a
+ // parent service has already created a copy then
+ // we should stick to using that
+ var options = initialOptions || {};
+ if (!options.$$prepared) {
+ options = copy(options);
+ }
// there is no point in applying the styles since
// there is no animation that goes on at all in
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 98c233206f65..1049aa6c8c53 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -1052,6 +1052,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
this.component = function registerComponent(name, options) {
+ var controller = options.controller || function() {};
+
function factory($injector) {
function makeInjectable(fn) {
if (isFunction(fn) || isArray(fn)) {
@@ -1065,7 +1067,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var template = (!options.template && !options.templateUrl ? '' : options.template);
return {
- controller: options.controller || function() {},
+ controller: controller,
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
template: makeInjectable(template),
templateUrl: makeInjectable(options.templateUrl),
diff --git a/src/ng/controller.js b/src/ng/controller.js
index 4e64f3417a88..389f869c9888 100644
--- a/src/ng/controller.js
+++ b/src/ng/controller.js
@@ -3,7 +3,7 @@
var $controllerMinErr = minErr('$controller');
-var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
+var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
function identifierForController(controller, ident) {
if (ident && isString(ident)) return ident;
if (isString(controller)) {
diff --git a/src/ngAnimate/animateCss.js b/src/ngAnimate/animateCss.js
index 278814b1f744..b9757f27777c 100644
--- a/src/ngAnimate/animateCss.js
+++ b/src/ngAnimate/animateCss.js
@@ -447,10 +447,14 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
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);
+ // all of the animation functions should create
+ // a copy of the options data, however, if a
+ // parent service has already created a copy then
+ // we should stick to using that
+ var options = initialOptions || {};
+ if (!options.$$prepared) {
+ options = prepareAnimationOptions(copy(options));
+ }
var restoreStyles = {};
var node = getDomNode(element);
@@ -460,8 +464,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return closeAndReturnNoopAnimator();
}
- options = prepareAnimationOptions(options);
-
var temporaryStyles = [];
var classes = element.attr('class');
var styles = packageStyles(options);
diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js
index 6997d83a5dd0..500aa9ac8782 100644
--- a/src/ngMock/angular-mocks.js
+++ b/src/ngMock/angular-mocks.js
@@ -797,7 +797,10 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
var animateJsConstructor = function() {
var animator = $delegate.apply($delegate, arguments);
- runners.push(animator);
+ // If no javascript animation is found, animator is undefined
+ if (animator) {
+ runners.push(animator);
+ }
return animator;
};
@@ -2161,6 +2164,49 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
};
}];
+/**
+ * @ngdoc service
+ * @name $componentController
+ * @description
+ * A service that can be used to create instances of component controllers.
+ *
+ * Be aware that the controller will be instantiated and attached to the scope as specified in
+ * the component definition object. That means that you must always provide a `$scope` object
+ * in the `locals` param.
+ *
+ * @param {string} componentName the name of the component whose controller we want to instantiate
+ * @param {Object} locals Injection locals for Controller.
+ * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
+ * to simulate the `bindToController` feature and simplify certain kinds of tests.
+ * @param {string=} ident Override the property name to use when attaching the controller to the scope.
+ * @return {Object} Instance of requested controller.
+ */
+angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) {
+ return {
+ $get: ['$controller','$injector', function($controller,$injector) {
+ return function $componentController(componentName, locals, bindings, ident) {
+ // get all directives associated to the component name
+ var directives = $injector.get(componentName + 'Directive');
+ // look for those directives that are components
+ var candidateDirectives = directives.filter(function(directiveInfo) {
+ // components have controller, controllerAs and restrict:'E' compatible
+ return directiveInfo.controller && directiveInfo.controllerAs && directiveInfo.restrict.indexOf('E') >= 0;
+ });
+ // check if valid directives found
+ if (candidateDirectives.length === 0) {
+ throw new Error('No component found');
+ }
+ if (candidateDirectives.length > 1) {
+ throw new Error('Too many components found');
+ }
+ // get the info of the component
+ var directiveInfo = candidateDirectives[0];
+ return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs);
+ };
+ }]
+ };
+}];
+
/**
* @ngdoc module
@@ -2184,7 +2230,8 @@ angular.module('ngMock', ['ng']).provider({
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
- $rootElement: angular.mock.$RootElementProvider
+ $rootElement: angular.mock.$RootElementProvider,
+ $componentController: angular.mock.$ComponentControllerProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
diff --git a/test/helpers/privateMocks.js b/test/helpers/privateMocks.js
index 637f19ebf97c..ae57f116cc0e 100644
--- a/test/helpers/privateMocks.js
+++ b/test/helpers/privateMocks.js
@@ -40,9 +40,8 @@ function browserSupportsCssAnimations() {
return true;
}
-function createMockStyleSheet(doc, wind) {
+function createMockStyleSheet(doc, prefix) {
doc = doc ? doc[0] : document;
- wind = wind || window;
var node = doc.createElement('style');
var head = doc.getElementsByTagName('head')[0];
@@ -63,6 +62,18 @@ function createMockStyleSheet(doc, wind) {
}
},
+ addPossiblyPrefixedRule: function(selector, styles) {
+ if (prefix) {
+ var prefixedStyles = styles.split(/\s*;\s*/g).map(function(style) {
+ return !style ? '' : prefix + style;
+ }).join('; ');
+
+ this.addRule(selector, prefixedStyles);
+ }
+
+ this.addRule(selector, styles);
+ },
+
destroy: function() {
head.removeChild(node);
}
diff --git a/test/helpers/privateMocksSpec.js b/test/helpers/privateMocksSpec.js
index 495dcb60c6ef..c584b1d33c39 100644
--- a/test/helpers/privateMocksSpec.js
+++ b/test/helpers/privateMocksSpec.js
@@ -211,7 +211,7 @@ describe('private mocks', function() {
var doc = $document[0];
var count = doc.styleSheets.length;
- var stylesheet = createMockStyleSheet($document, $window);
+ var stylesheet = createMockStyleSheet($document);
var elm;
runs(function() {
expect(doc.styleSheets.length).toBe(count + 1);
diff --git a/test/ng/animateCssSpec.js b/test/ng/animateCssSpec.js
index f0a027805cfa..dcc37b7fc9c9 100644
--- a/test/ng/animateCssSpec.js
+++ b/test/ng/animateCssSpec.js
@@ -31,6 +31,28 @@ describe("$animateCss", function() {
expect(copiedOptions).toEqual(initialOptions);
}));
+ it("should not create a copy of the provided options if they have already been prepared earlier",
+ inject(function($animateCss, $$rAF) {
+
+ var options = {
+ from: { height: '50px' },
+ to: { width: '50px' },
+ addClass: 'one',
+ removeClass: 'two'
+ };
+
+ options.$$prepared = true;
+ var runner = $animateCss(element, options).start();
+ runner.end();
+
+ $$rAF.flush();
+
+ expect(options.addClass).toBeFalsy();
+ expect(options.removeClass).toBeFalsy();
+ expect(options.to).toBeFalsy();
+ expect(options.from).toBeFalsy();
+ }));
+
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');
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 884f6c8846c4..d79d55a0774c 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -230,6 +230,9 @@ describe('$compile', function() {
describe('svg namespace transcludes', function() {
+ var ua = window.navigator.userAgent;
+ var isEdge = /Edge/.test(ua);
+
// this method assumes some sort of sized SVG element is being inspected.
function assertIsValidSvgCircle(elem) {
expect(isUnknownElement(elem)).toBe(false);
@@ -300,17 +303,19 @@ describe('$compile', function() {
}));
// NOTE: This test may be redundant.
- it('should handle custom svg containers that transclude to foreignObject' +
- ' that transclude to custom svg containers that transclude to custom elements', inject(function() {
- element = jqLite('
' +
- '' +
- '
');
- $compile(element.contents())($rootScope);
- document.body.appendChild(element[0]);
-
- var circle = element.find('circle');
- assertIsValidSvgCircle(circle[0]);
- }));
+ if (!isEdge) {
+ it('should handle custom svg containers that transclude to foreignObject' +
+ ' that transclude to custom svg containers that transclude to custom elements', inject(function() {
+ element = jqLite('
' +
+ '' +
+ '
');
+ $compile(element.contents())($rootScope);
+ document.body.appendChild(element[0]);
+
+ var circle = element.find('circle');
+ assertIsValidSvgCircle(circle[0]);
+ }));
+ }
}
it('should handle directives with templates that manually add the transclude further down', inject(function() {
diff --git a/test/ng/controllerSpec.js b/test/ng/controllerSpec.js
index 839e3310c5e6..253623d05a42 100644
--- a/test/ng/controllerSpec.js
+++ b/test/ng/controllerSpec.js
@@ -209,5 +209,16 @@ describe('$controller', function() {
"Badly formed controller string 'ctrl as'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
+
+
+ it('should allow identifiers containing `$`', function() {
+ var scope = {};
+
+ $controllerProvider.register('FooCtrl', function() { this.mark = 'foo'; });
+
+ var foo = $controller('FooCtrl as $foo', {$scope: scope});
+ expect(scope.$foo).toBe(foo);
+ expect(scope.$foo.mark).toBe('foo');
+ });
});
});
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 58689c0eea5f..b8ba4ed924d2 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -1196,7 +1196,7 @@ describe('input', function() {
it('should validate if max is empty', function() {
$rootScope.maxVal = undefined;
- $rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
+ $rootScope.value = new Date(3000, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js
index 4523718b64f0..e91939ca3194 100644
--- a/test/ng/directive/ngRepeatSpec.js
+++ b/test/ng/directive/ngRepeatSpec.js
@@ -1491,11 +1491,11 @@ describe('ngRepeat animations', function() {
}));
it('should not change the position of the block that is being animated away via a leave animation',
- inject(function($compile, $rootScope, $animate, $document, $window, $sniffer, $timeout) {
+ inject(function($compile, $rootScope, $animate, $document, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
var item;
- var ss = createMockStyleSheet($document, $window);
+ var ss = createMockStyleSheet($document);
try {
diff --git a/test/ng/snifferSpec.js b/test/ng/snifferSpec.js
index c16d4802d966..b1fafceff36d 100644
--- a/test/ng/snifferSpec.js
+++ b/test/ng/snifferSpec.js
@@ -88,7 +88,9 @@ describe('$sniffer', function() {
inject(function($sniffer, $window) {
var expectedPrefix;
var ua = $window.navigator.userAgent.toLowerCase();
- if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
+ if (/edge/i.test(ua)) {
+ expectedPrefix = 'Ms';
+ } else if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
expectedPrefix = 'Webkit';
} else if (/firefox/i.test(ua)) {
expectedPrefix = 'Moz';
diff --git a/test/ngAnimate/animateCssDriverSpec.js b/test/ngAnimate/animateCssDriverSpec.js
index 97567e4c053e..abc4eefa7a89 100644
--- a/test/ngAnimate/animateCssDriverSpec.js
+++ b/test/ngAnimate/animateCssDriverSpec.js
@@ -69,11 +69,11 @@ describe("ngAnimate $$animateCssDriver", function() {
element = jqLite('');
- return function($$animateCssDriver, $document, $window) {
+ return function($$animateCssDriver, $document) {
driver = function(details, cb) {
return $$animateCssDriver(details, cb || noop);
};
- ss = createMockStyleSheet($document, $window);
+ ss = createMockStyleSheet($document);
};
}));
diff --git a/test/ngAnimate/animateCssSpec.js b/test/ngAnimate/animateCssSpec.js
index e43dd89a912c..7de34ee58615 100644
--- a/test/ngAnimate/animateCssSpec.js
+++ b/test/ngAnimate/animateCssSpec.js
@@ -12,6 +12,13 @@ describe("ngAnimate $animateCss", function() {
: expect(className).not.toMatch(regex);
}
+ function getPossiblyPrefixedStyleValue(element, styleProp) {
+ var value = element.css(prefix + styleProp);
+ if (isUndefined(value)) value = element.css(styleProp);
+
+ return value;
+ }
+
function keyframeProgress(element, duration, delay) {
browserTrigger(element, 'animationend',
{ timeStamp: Date.now() + ((delay || 1) * 1000), elapsedTime: duration });
@@ -28,9 +35,10 @@ describe("ngAnimate $animateCss", function() {
var ss, prefix, triggerAnimationStartFrame;
beforeEach(module(function() {
- return function($document, $window, $sniffer, $$rAF, $animate) {
+ return function($document, $sniffer, $$rAF, $animate) {
prefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
- ss = createMockStyleSheet($document, $window);
+ ss = createMockStyleSheet($document, prefix);
+
$animate.enabled(true);
triggerAnimationStartFrame = function() {
$$rAF.flush();
@@ -325,7 +333,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
runner.pause();
- expect(element.css(prefix + 'animation-play-state')).toEqual('paused');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toEqual('paused');
runner.resume();
expect(element.attr('style')).toBeFalsy();
}));
@@ -346,7 +354,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
runner.pause();
- expect(element.css(prefix + 'animation-play-state')).toEqual('paused');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toEqual('paused');
runner.end();
expect(element.attr('style')).toBeFalsy();
}));
@@ -784,8 +792,8 @@ describe("ngAnimate $animateCss", function() {
jqLite($document[0].body).append($rootElement);
- ss.addRule('.ng-enter-stagger', prefix + 'animation-delay:0.2s');
- ss.addRule('.ng-enter', prefix + 'animation:my_animation 2s;');
+ ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'animation-delay:0.2s');
+ ss.addPossiblyPrefixedRule('.ng-enter', 'animation:my_animation 2s;');
var i, element, elements = [];
for (i = 0; i < 5; i++) {
@@ -803,7 +811,7 @@ describe("ngAnimate $animateCss", function() {
if (i === 0) { // the first element is always run right away
expect(element.attr('style')).toBeFalsy();
} else {
- expect(element.css(prefix + 'animation-play-state')).toBe('paused');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toBe('paused');
}
}
}));
@@ -879,7 +887,7 @@ describe("ngAnimate $animateCss", function() {
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
- ss.addRule('.transition-animation', prefix + 'animation:2s 5s my_animation;');
+ ss.addPossiblyPrefixedRule('.transition-animation', 'animation: 2s 5s my_animation;');
for (var i = 0; i < 5; i++) {
var elm = jqLite('');
@@ -898,11 +906,11 @@ describe("ngAnimate $animateCss", function() {
jqLite($document[0].body).append($rootElement);
- ss.addRule('.ng-enter-stagger', 'transition-delay:0.5s;' +
- prefix + 'animation-delay:1s');
+ ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay: 0.5s; ' +
+ 'animation-delay: 1s');
- ss.addRule('.ng-enter', 'transition:10s linear all;' +
- prefix + 'animation:my_animation 20s');
+ ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 10s linear all; ' +
+ 'animation: 20s my_animation');
var i, elm, elements = [];
for (i = 0; i < 5; i++) {
@@ -1258,14 +1266,14 @@ describe("ngAnimate $animateCss", function() {
// At this point, the animation should still be running (closing timeout is 7500ms ... duration * 1.5 + delay => 7.5)
$timeout.flush(7000);
- expect(element.css(prefix + 'transition-delay')).toBe('3s');
- expect(element.css(prefix + 'transition-duration')).toBe('3s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBe('3s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBe('3s');
// Let's flush the remaining amount of time for the timeout timer to kick in
$timeout.flush(500);
- expect(element.css(prefix + 'transition-duration')).toBeOneOf('', '0s');
- expect(element.css(prefix + 'transition-delay')).toBeOneOf('', '0s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBeOneOf('', '0s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('', '0s');
}));
it("should ignore a boolean options.delay value for the closing timeout",
@@ -1291,14 +1299,14 @@ describe("ngAnimate $animateCss", function() {
// At this point, the animation should still be running (closing timeout is 4500ms ... duration * 1.5 => 4.5)
$timeout.flush(4000);
- expect(element.css(prefix + 'transition-delay')).toBeOneOf('initial', '0s');
- expect(element.css(prefix + 'transition-duration')).toBe('3s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('initial', '0s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBe('3s');
// Let's flush the remaining amount of time for the timeout timer to kick in
$timeout.flush(500);
- expect(element.css(prefix + 'transition-duration')).toBeOneOf('', '0s');
- expect(element.css(prefix + 'transition-delay')).toBeOneOf('', '0s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBeOneOf('', '0s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('', '0s');
}));
});
@@ -2028,6 +2036,28 @@ describe("ngAnimate $animateCss", function() {
expect(copiedOptions).toEqual(initialOptions);
}));
+ it("should not create a copy of the provided options if they have already been prepared earlier",
+ inject(function($animate, $animateCss) {
+
+ var options = {
+ from: { height: '50px' },
+ to: { width: '50px' },
+ addClass: 'one',
+ removeClass: 'two'
+ };
+
+ options.$$prepared = true;
+ var runner = $animateCss(element, options).start();
+ runner.end();
+
+ $animate.flush();
+
+ expect(options.addClass).toBeFalsy();
+ expect(options.removeClass).toBeFalsy();
+ expect(options.to).toBeFalsy();
+ expect(options.from).toBeFalsy();
+ }));
+
describe("[$$skipPreparationClasses]", function() {
it('should not apply and remove the preparation classes to the element when true',
inject(function($animateCss) {
@@ -2104,7 +2134,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-duration')).toEqual('5s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5s');
}));
it("should remove all inline keyframe styling when an animation completes if a custom duration was applied",
@@ -2145,7 +2175,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toEqual('5s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('5s');
browserTrigger(element, 'animationend',
{ timeStamp: Date.now() + 5000, elapsedTime: 1.5 });
@@ -2299,7 +2329,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toEqual('400s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('400s');
expect(element.attr('style')).not.toContain('transition-delay');
}));
@@ -2323,7 +2353,7 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toEqual('10s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('10s');
expect(element.css('transition-delay')).toEqual('10s');
}));
@@ -2363,7 +2393,7 @@ describe("ngAnimate $animateCss", function() {
var assertionsRun = false;
classSpy = function() {
assertionsRun = true;
- expect(element.css(prefix + 'animation-delay')).toEqual('2s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('2s');
expect(element.css('transition-delay')).toEqual('2s');
expect(element).not.toHaveClass('superman');
};
@@ -2397,8 +2427,8 @@ describe("ngAnimate $animateCss", function() {
it("should consider a negative value when delay:true is used with a keyframe animation",
inject(function($animateCss, $rootElement) {
- ss.addRule('.ng-enter', prefix + 'animation:2s keyframe_animation; ' +
- prefix + 'animation-delay: -1s;');
+ ss.addPossiblyPrefixedRule('.ng-enter', 'animation: 2s keyframe_animation; ' +
+ 'animation-delay: -1s;');
var options = {
delay: true,
@@ -2411,7 +2441,7 @@ describe("ngAnimate $animateCss", function() {
animator.start();
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toContain('-1s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toContain('-1s');
}));
they("should consider a negative value when a negative option delay is provided for a $prop animation", {
@@ -2421,17 +2451,17 @@ describe("ngAnimate $animateCss", function() {
css: 'transition:2s linear all'
};
},
- 'keyframe': function(prefix) {
+ 'keyframe': function() {
return {
- prop: prefix + 'animation-delay',
- css: prefix + 'animation:2s keyframe_animation'
+ prop: 'animation-delay',
+ css: 'animation: 2s keyframe_animation'
};
}
}, function(testDetailsFactory) {
inject(function($animateCss, $rootElement) {
var testDetails = testDetailsFactory(prefix);
- ss.addRule('.ng-enter', testDetails.css);
+ ss.addPossiblyPrefixedRule('.ng-enter', testDetails.css);
var options = {
delay: -2,
event: 'enter',
@@ -2443,7 +2473,7 @@ describe("ngAnimate $animateCss", function() {
animator.start();
triggerAnimationStartFrame();
- expect(element.css(testDetails.prop)).toContain('-2s');
+ expect(getPossiblyPrefixedStyleValue(element, testDetails.prop)).toContain('-2s');
});
});
@@ -2454,18 +2484,18 @@ describe("ngAnimate $animateCss", function() {
css: 'transition:5s linear all; transition-delay: -2s'
};
},
- 'animation': function(prefix) {
+ 'animation': function() {
return {
event: 'animationend',
- css: prefix + 'animation:5s keyframe_animation; ' + prefix + 'animation-delay: -2s;'
+ css: 'animation: 5s keyframe_animation; animation-delay: -2s;'
};
}
}, function(testDetailsFactory) {
inject(function($animateCss, $rootElement) {
- var testDetails = testDetailsFactory(prefix);
+ var testDetails = testDetailsFactory();
var event = testDetails.event;
- ss.addRule('.ng-enter', testDetails.css);
+ ss.addPossiblyPrefixedRule('.ng-enter', testDetails.css);
var options = { event: 'enter', structural: true };
var animator = $animateCss(element, options);
@@ -2571,15 +2601,15 @@ describe("ngAnimate $animateCss", function() {
$animateCss(element, options).start();
triggerAnimationStartFrame();
- expect(element.css(prefix + 'transition-delay')).not.toEqual('4s');
- expect(element.css(prefix + 'transition-duration')).not.toEqual('6s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).not.toEqual('4s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).not.toEqual('6s');
options.to = { color: 'brown' };
$animateCss(element, options).start();
triggerAnimationStartFrame();
- expect(element.css(prefix + 'transition-delay')).toEqual('4s');
- expect(element.css(prefix + 'transition-duration')).toEqual('6s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toEqual('4s');
+ expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toEqual('6s');
}));
});
@@ -2652,9 +2682,9 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toEqual('50s');
- expect(element.css(prefix + 'animation-duration')).toEqual('5.5s');
- expect(element.css(prefix + 'animation-name')).toEqual('my_animation');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('50s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5.5s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-name')).toEqual('my_animation');
}));
it("should be able to execute the animation if it is the only provided value",
@@ -2669,9 +2699,9 @@ describe("ngAnimate $animateCss", function() {
animator.start();
triggerAnimationStartFrame();
- expect(element.css(prefix + 'animation-delay')).toEqual('10s');
- expect(element.css(prefix + 'animation-duration')).toEqual('5.5s');
- expect(element.css(prefix + 'animation-name')).toEqual('my_animation');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('10s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5.5s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-name')).toEqual('my_animation');
}));
});
@@ -2964,7 +2994,7 @@ describe("ngAnimate $animateCss", function() {
expect(element.css('transition-duration')).toMatch('10s');
- expect(element.css(prefix + 'animation-duration')).toEqual('10s');
+ expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('10s');
}));
});
@@ -3004,7 +3034,7 @@ describe("ngAnimate $animateCss", function() {
inject(function($animateCss) {
ss.addRule('.red', 'transition: 1s linear all;');
- ss.addRule('.blue', prefix + 'animation:my_keyframe 1s;');
+ ss.addPossiblyPrefixedRule('.blue', 'animation: 1s my_keyframe;');
var easing = 'ease-out';
var animator = $animateCss(element, { addClass: 'red blue', easing: easing });
animator.start();
diff --git a/test/ngAnimate/integrationSpec.js b/test/ngAnimate/integrationSpec.js
index 5c67897c8492..f3758250a00a 100644
--- a/test/ngAnimate/integrationSpec.js
+++ b/test/ngAnimate/integrationSpec.js
@@ -7,10 +7,10 @@ describe('ngAnimate integration tests', function() {
var element, html, ss;
beforeEach(module(function() {
- return function($rootElement, $document, $window, $animate) {
+ return function($rootElement, $document, $animate) {
$animate.enabled(true);
- ss = createMockStyleSheet($document, $window);
+ ss = createMockStyleSheet($document);
var body = jqLite($document[0].body);
html = function(element) {
@@ -28,6 +28,27 @@ describe('ngAnimate integration tests', function() {
describe('CSS animations', function() {
if (!browserSupportsCssAnimations()) return;
+ it("should only create a single copy of the provided animation options",
+ inject(function($rootScope, $rootElement, $animate) {
+
+ ss.addRule('.animate-me', 'transition:2s linear all;');
+
+ var element = jqLite('');
+ html(element);
+
+ var myOptions = {to: { 'color': 'red' }};
+
+ var spy = spyOn(window, 'copy');
+ expect(spy).not.toHaveBeenCalled();
+
+ var animation = $animate.leave(element, myOptions);
+ $rootScope.$digest();
+ $animate.flush();
+
+ expect(spy).toHaveBeenCalledOnce();
+ dealoc(element);
+ }));
+
they('should render an $prop animation',
['enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function(event) {
@@ -426,6 +447,32 @@ describe('ngAnimate integration tests', function() {
expect(element).not.toHaveClass('hide');
}));
+ it('should handle ng-if & ng-class with a class that is removed before its add animation has concluded', function() {
+ inject(function($animate, $rootScope, $compile, $timeout, $$rAF) {
+
+ ss.addRule('.animate-me', 'transition: all 0.5s;');
+
+ element = jqLite('');
+
+ html(element);
+ $rootScope.blue = true;
+ $rootScope.red = true;
+ $compile(element)($rootScope);
+ $rootScope.$digest();
+
+ var child = element.find('div');
+
+ // Trigger class removal before the add animation has been concluded
+ $rootScope.blue = false;
+ $animate.closeAndFlush();
+
+ expect(child).toHaveClass('red');
+ expect(child).not.toHaveClass('blue');
+ });
+ });
});
describe('JS animations', function() {
diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js
index 14fb543eb374..30b096a35b69 100644
--- a/test/ngMock/angular-mocksSpec.js
+++ b/test/ngMock/angular-mocksSpec.js
@@ -1857,6 +1857,198 @@ describe('ngMock', function() {
});
});
});
+
+
+ describe('$componentController', function() {
+ it('should instantiate a simple controller defined inline in a component', function() {
+ function TestController($scope, a, b) {
+ this.$scope = $scope;
+ this.a = a;
+ this.b = b;
+ }
+ module(function($compileProvider) {
+ $compileProvider.component('test', {
+ controller: TestController
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var $scope = {};
+ var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' });
+ expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' });
+ expect($scope.$ctrl).toBe(ctrl);
+ });
+ });
+
+ it('should instantiate a controller with $$inject annotation defined inline in a component', function() {
+ function TestController(x, y, z) {
+ this.$scope = x;
+ this.a = y;
+ this.b = z;
+ }
+ TestController.$inject = ['$scope', 'a', 'b'];
+ module(function($compileProvider) {
+ $compileProvider.component('test', {
+ controller: TestController
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var $scope = {};
+ var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' });
+ expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' });
+ expect($scope.$ctrl).toBe(ctrl);
+ });
+ });
+
+ it('should instantiate a named controller defined in a component', function() {
+ function TestController($scope, a, b) {
+ this.$scope = $scope;
+ this.a = a;
+ this.b = b;
+ }
+ module(function($controllerProvider, $compileProvider) {
+ $controllerProvider.register('TestController', TestController);
+ $compileProvider.component('test', {
+ controller: 'TestController'
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var $scope = {};
+ var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' });
+ expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' });
+ expect($scope.$ctrl).toBe(ctrl);
+ });
+ });
+
+ it('should instantiate a named controller with `controller as` syntax defined in a component', function() {
+ function TestController($scope, a, b) {
+ this.$scope = $scope;
+ this.a = a;
+ this.b = b;
+ }
+ module(function($controllerProvider, $compileProvider) {
+ $controllerProvider.register('TestController', TestController);
+ $compileProvider.component('test', {
+ controller: 'TestController as testCtrl'
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var $scope = {};
+ var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' });
+ expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' });
+ expect($scope.testCtrl).toBe(ctrl);
+ });
+ });
+
+ it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name but not \'E\' restrictness', function() {
+ function TestController() {
+ this.r = 6779;
+ }
+ module(function($compileProvider) {
+ $compileProvider.directive('test', function() {
+ return { restrict: 'A' };
+ });
+ $compileProvider.component('test', {
+ controller: TestController
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var ctrl = $componentController('test', { $scope: {} });
+ expect(ctrl).toEqual({ r: 6779 });
+ });
+ });
+
+ it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name and \'E\' restrictness but no controller', function() {
+ function TestController() {
+ this.r = 22926;
+ }
+ module(function($compileProvider) {
+ $compileProvider.directive('test', function() {
+ return { restrict: 'E' };
+ });
+ $compileProvider.component('test', {
+ controller: TestController
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var ctrl = $componentController('test', { $scope: {} });
+ expect(ctrl).toEqual({ r: 22926 });
+ });
+ });
+
+ it('should instantiate the controller of the directive with controller if there are more directives', function() {
+ function TestController() {
+ this.r = 18842;
+ }
+ module(function($compileProvider) {
+ $compileProvider.directive('test', function() {
+ return { };
+ });
+ $compileProvider.directive('test', function() {
+ return {
+ controller: TestController,
+ controllerAs: '$ctrl'
+ };
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ var ctrl = $componentController('test', { $scope: {} });
+ expect(ctrl).toEqual({ r: 18842 });
+ });
+ });
+
+ it('should fail if there is no directive with restrict:\'E\' compatible and controller', function() {
+ function TestController() {
+ this.r = 31145;
+ }
+ module(function($compileProvider) {
+ $compileProvider.directive('test', function() {
+ return {
+ restrict: 'AC',
+ controller: TestController
+ };
+ });
+ $compileProvider.directive('test', function() {
+ return {
+ restrict: 'E',
+ controller: TestController
+ };
+ });
+ $compileProvider.directive('test', function() {
+ return { restrict: 'E' };
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ expect(function() {
+ $componentController('test', { $scope: {} });
+ }).toThrow('No component found');
+ });
+ });
+
+ it('should fail if there more than two components with same name', function() {
+ function TestController($scope, a, b) {
+ this.$scope = $scope;
+ this.a = a;
+ this.b = b;
+ }
+ module(function($compileProvider) {
+ $compileProvider.directive('test', function() {
+ return {
+ controller: TestController,
+ controllerAs: '$ctrl'
+ };
+ });
+ $compileProvider.component('test', {
+ controller: TestController
+ });
+ });
+ inject(function($componentController, $rootScope) {
+ expect(function() {
+ var $scope = {};
+ $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' });
+ }).toThrow('Too many components found');
+ });
+ });
+ });
});
@@ -1964,8 +2156,8 @@ describe('ngMockE2E', function() {
}
});
- return function($animate, $rootElement, $document, $rootScope, $window) {
- ss = createMockStyleSheet($document, $window);
+ return function($animate, $rootElement, $document, $rootScope) {
+ ss = createMockStyleSheet($document);
element = angular.element('');
$rootElement.append(element);
@@ -2148,12 +2340,36 @@ describe('ngMockE2E', function() {
expect(animationLog).toEqual(['start leave', 'end leave']);
}));
+ it('should not throw when a regular animation has no javascript animation',
+ inject(function($animate, $$animation, $rootElement) {
+
+ if (!browserSupportsCssAnimations()) return;
+
+ var element = jqLite('');
+ $rootElement.append(element);
+
+ // Make sure the animation has valid $animateCss options
+ $$animation(element, null, {
+ from: { background: 'red' },
+ to: { background: 'blue' },
+ duration: 1,
+ transitionStyle: 'all 1s'
+ });
+
+ expect(function() {
+ $animate.closeAndFlush();
+ }).not.toThrow();
+
+ dealoc(element);
+ }));
+
it('should throw an error if there are no animations to close and flush',
inject(function($animate) {
expect(function() {
$animate.closeAndFlush();
}).toThrow('No pending animations ready to be closed or flushed');
+
}));
});
});
diff --git a/test/ngSanitize/sanitizeSpec.js b/test/ngSanitize/sanitizeSpec.js
index b5c9d33bc0b2..6455ba94d6ee 100644
--- a/test/ngSanitize/sanitizeSpec.js
+++ b/test/ngSanitize/sanitizeSpec.js
@@ -1,6 +1,8 @@
'use strict';
describe('HTML', function() {
+ var ua = window.navigator.userAgent;
+ var isChrome = /Chrome/.test(ua) && !/Edge/.test(ua);
var expectHTML;
@@ -222,7 +224,7 @@ describe('HTML', function() {
.toEqual('');
});
- if (/Chrome/.test(window.navigator.userAgent)) {
+ if (isChrome) {
it('should prevent mXSS attacks', function() {
expectHTML('CLICKME').toBe('CLICKME');
});
@@ -245,7 +247,8 @@ describe('HTML', function() {
expectHTML('')
.toBeOneOf('',
'',
- '');
+ '',
+ '');
});
it('should not ignore white-listed svg camelCased attributes', function() {
@@ -283,6 +286,7 @@ describe('HTML', function() {
it('should not accept SVG animation tags', function() {
expectHTML('')
.toBeOneOf('',
+ '',
'');
expectHTML('