Compare commits

...

14 Commits

Author SHA1 Message Date
Peter Bacon Darwin dcfcf81893 docs(CHANGELOG): add release notes for 1.6.0-rc.2 2016-11-24 21:30:56 +00:00
Peter Bacon Darwin d7cc863105 docs(CHANGELOG): add release notes for 1.5.9 2016-11-24 20:16:03 +00:00
Martin Probst 6e91f9c25d style: fix CI failure (#15429) 2016-11-23 23:04:05 -08:00
Martin Probst 7f1b8bdfe1 feat(security): explicitly whitelist URL schemes for bootstrap. (#15427)
Many browsers have some extension URL scheme. It is unclear how many of
those have the security issue of allowing parser-inserted loads of
extension URLs.

To be conservative, this code whitelists the URL schemes that are known
to be subject to CSP, i.e. the ones that are expected and safe.
2016-11-23 15:44:43 -08:00
Michał Gołębiowski cc92da0d67 chore(*): cleanup msie handling; add support comments
1. The conditions checking the msie variable value have been simplified.
There is e.g. no point to check if `msie <= 11` since there IE 12 won't ever
exist.
2. Edge UA-sniffing has been added to tests (only!) where appropriate
3. Support comments for IE/Edge have been added.

Closes #15407
2016-11-23 14:42:04 +00:00
Packt c9bb5b9fa4 docs(external-resources): add new book link and alphabetize
Closes #15421
2016-11-23 14:39:06 +00:00
Michał Gołębiowski c54921008d chore(ngAnimate): cleanup vendor prefixes handling in tests
1. Change all transition/transform/animation-related ss.addRule to
   ss.addPossiblyPrefixedRule to account for the -webkit- prefix.
2. Remove manually added -webkit-prefixed rules in favor of automatically
   handling them in ss.addPossiblyPrefixedRule.

Closes #15406
2016-11-23 14:28:12 +01:00
Martin Staffa 69f59f2d01 docs($compile, guide/compiler): add "double compilation" known issue
Related #15278
Closes #15392
2016-11-23 13:52:48 +01:00
Martin Staffa f4fb6e0983 perf(*): don't trigger digests after enter/leave of structural directives
ngIf, ngInclude, ngSwitch, and ngView now use the `done` callback functions on animation runners returned
by leave/enter animations to do internal cleanup (and $anchorScroll for ngInclude and ngView).
Previously, they were using promise callbacks (`then`), which caused an unnessecary digest.

Background:

In https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9, animation
promises where introduced instead of animation callbacks. These promises were however not tied to
the digest cycle, so you had to manually call `$apply` inside them.

This was changed in https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef,
so animation promise callbacks would always trigger a `$digest`. This meant that several built-in
directives would now trigger additional digests on leave (ngIf, ngSwitch) or enter/leave (ngInclude,
ngView). The `done` callback, which receives a single argument indicating success / failure was
introduced to allow digest-less responses, but wasn't applied to these directives.

Note that this applies to all calls to $animate.enter/leave, even if ngAnimate isn't included, and
no actual animations are running, because the animation runner code is in the core ng module.

Fixes #15322
Closes #15345
2016-11-23 13:30:41 +01:00
Martin Staffa a18be15137 style(ngIfSpec.js, ngIncludeSpec.js): add top level describe 2016-11-23 13:30:41 +01:00
Martin Staffa 05a9d3a73c docs(*): add more info deprecation versions 2016-11-23 00:10:54 +01:00
Georgios Kalpakas 60035f597c docs(guide/directive): minor wording and styling fixes 2016-11-22 14:49:26 +02:00
Georgios Kalpakas 0af21a48e5 docs(CHANGELOG.md): minor fixes/tweaks 2016-11-22 09:48:08 +02:00
Santi Albo 991a2b30e0 fix($sce): fix adjustMatcher to replace multiple '*' and '**'
`adjustMatcher` was only replacing the first occurrences of '*' and '**'
that were found in whitelisted and blacklisted url strings.

Closes ##7897
2016-11-21 14:51:48 +00:00
34 changed files with 1608 additions and 1216 deletions
+85 -3
View File
@@ -1,4 +1,84 @@
<a name="1.6.0-rc.2"></a>
# 1.6.0-rc.2 safety-insurance (2016-11-24)
## Security Fixes
- **bootstrap:** explicitly whitelist URL schemes for bootstrap. (#15427)
([7f1b8b](https://github.com/angular/angular.js/commit/7f1b8bdfe1043871c5ead2ec602efc41e0de5e53))
## Bug Fixes
- **$sce:** fix `adjustMatcher` to replace multiple '*' and '**'
([991a2b](https://github.com/angular/angular.js/commit/991a2b30e00aed1d312e29555e356a795f9e3d62))
## Performance Improvements
- ***:** don't trigger digests after enter/leave of structural directives
([f4fb6e](https://github.com/angular/angular.js/commit/f4fb6e0983a6a700dc4a246a913504550b55f1e9)
[#15322](https://github.com/angular/angular.js/issues/15322))
<a name="1.5.9"></a>
# 1.5.9 timeturning-lockdown (2016-11-24)
This is an interim release primarily to publish some security fixes, in particular a modification to
ensure that Angular 1 can pass the linter checks for Mozilla add-ons.
## Security Fixes
- **bootstrap:**
- do not auto-bootstrap when loaded from an extension
([6ce291](https://github.com/angular/angular.js/commit/6ce2913d99bb0dade6027ba9733295d0aa13b242))
- explicitly whitelist URL schemes for bootstrap (#15427)
([4edd2d](https://github.com/angular/angular.js/commit/4edd2d95c11819ece2dda6e65f95f32638fda218))
- **$location:** throw if the path starts with double (back)slashes
([353e3a](https://github.com/angular/angular.js/commit/353e3a6cd8b3a785b5f73a38236155621048522f))
- **$sniffer:** don't use `history.pushState` in sandboxed Chrome Packaged Apps
([367da5](https://github.com/angular/angular.js/commit/367da583bc12e6f5f01edf757305409cf63fb1f4))
- **$parse:**
- block assigning to fields of a constructor prototype
([d7e31b](https://github.com/angular/angular.js/commit/d7e31b5dc71253edb22190a5850034934e7b778a)
[#14939](https://github.com/angular/angular.js/issues/14939))
- correctly escape unsafe identifier characters
([b01460](https://github.com/angular/angular.js/commit/b014607030835358ed7887e9fd1724cdada56690))
- **$compile:**
- ensure that hidden input values are correct after history.back
([6a2488](https://github.com/angular/angular.js/commit/6a24885771cf8c140b5d2895e92b321e60d86b55))
- lower the $sce context for `src` on video, audio, source, track
([68fb70](https://github.com/angular/angular.js/commit/68fb70ed295119d7b00c670d796c1b4186091adb))
## New Features
- **input:**
- add support for binding to `input[range]`
([2e7121](https://github.com/angular/angular.js/commit/2e7121b8e4dcac23f28e2375e775ca56b6baf252))
- make support for `input[range]` opt-in
([07b876](https://github.com/angular/angular.js/commit/07b8761233aaa3d719d94698296295e51c2a1077))
- fix `step` validation for `input[number][ng-range-input]`
([64f6a6](https://github.com/angular/angular.js/commit/64f6a616d401febc3f06309ed5a5efa46b131717)
[#15257](https://github.com/angular/angular.js/issues/15257))
- **ngMock/$httpBackend:** flush requests in any order
([098b6f](https://github.com/angular/angular.js/commit/098b6f519a53f6348127cd4ce09bca1423cbeb1a))
## Bug Fixes
- **$httpBackend:** complete the request on timeout
([549edc](https://github.com/angular/angular.js/commit/549edc9d0123d50657d5a03ba0c547cb0f91727f)
[#14969](https://github.com/angular/angular.js/issues/14969))
- **ngOptions:** remove selected attribute from unselected options
([d31b3a](https://github.com/angular/angular.js/commit/d31b3a65b65b73ab077026fc028ddf5b6232fba2)
[#14892](https://github.com/angular/angular.js/issues/14892))
## Performance Improvements
- **$parse:** improve performance of assignment expressions
([f83c3d](https://github.com/angular/angular.js/commit/f83c3dea23f910aed25dcf9b85fadf7f11a2a366))
- **$compile:** add provider option to turn off compilation of css class and comment directives
([775c24](https://github.com/angular/angular.js/commit/775c247085765e08845ae45ed19dd0120c61acc1))
<a name="1.6.0-rc.1"></a>
# 1.6.0-rc.1 proximity-warning (2016-11-21)
@@ -7,6 +87,7 @@
- **ngModelOptions:** allow options to be inherited from ancestor `ngModelOptions` ([296cfc](https://github.com/angular/angular.js/commit/296cfce40c25e9438bfa46a0eb27240707a10ffa) [#10922](https://github.com/angular/angular.js/issues/10922))
- **$compile:** set `preAssignBindingsEnabled` to false by default ([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858) [#15352](https://github.com/angular/angular.js/issues/15352))
## Bug Fixes
- **ngModelOptions:** handle debounce of `updateOn` triggers that are not in debounce list ([789790](https://github.com/angular/angular.js/commit/789790feee4d6c5b1f5d5b18ecb0ccf6edd36fb3))
@@ -14,7 +95,7 @@
- **$location:** throw if the path starts with double (back)slashes ([4aa953](https://github.com/angular/angular.js/commit/4aa9534b0fea732d6492a2863c3ee7e077c8d004))
- **core:** do not auto-bootstrap when loaded from an extension. ([0ff10e](https://github.com/angular/angular.js/commit/0ff10e1b56c6b7c4ac465e35c96a5886e294bac5))
- **input[radio]:** use strict comparison when evaluating checked-ness ([5ac7da](https://github.com/angular/angular.js/commit/5ac7daea72ec31cf337d1d21b13f0d17ff33994f) [#15288](https://github.com/angular/angular.js/issues/15288))
- **docsApp:** show correct version number in api index ([433c87](https://github.com/angular/angular.js/commit/433c8714f3d065a9a842502579b65d0388dd47ec))
## Reverts
@@ -25,7 +106,6 @@
- **ngOptions:** avoid calls to `element.value` ([3b7f29](https://github.com/angular/angular.js/commit/3b7f29ff63e8bf02327a1430dcc2a4c83915a206))
## Breaking Changes
- **feat($compile): set preAssignBindingsEnabled to false by default ([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858))**:
@@ -469,6 +549,8 @@ $scope.$watch('something', function() {
or possibly by using `Function.prototype.bind` or `angular.bind`.
### `aria/ngModel` due to:
- **[975a61](https://github.com/angular/angular.js/commit/975a6170efceb2a5e6377c57329731c0636eb8c8)**: do not overwrite the default `$isEmpty()` method for checkboxes
Custom `checkbox`-shaped controls (e.g. checkboxes, menuitemcheckboxes), no longer have a custom
@@ -854,7 +936,7 @@ The breaking change should be rare, as it relates to incorrect API use that shou
production apps in the first place.
- **fix($compile): secure `link[href]` as a `RESOURCE_URL`s in `$sce`. ([04cad4](https://github.com/angular/angular.js/commit/04cad41d26ebaf44b5ee0c29a152d61f235f3efa))**:
- **[04cad4](https://github.com/angular/angular.js/commit/04cad41d26ebaf44b5ee0c29a152d61f235f3efa)**: secure `link[href]` as a `RESOURCE_URL` in `$sce`
`link[href]` attributes are now protected via `$sce`, which prevents interpolated
values that fail the `RESOURCE_URL` context tests from being used in interpolation.
+103
View File
@@ -380,3 +380,106 @@ restrict: 'E',
replace: true
```
### Double Compilation, and how to avoid it
Double compilation occurs when an already compiled part of the DOM gets compiled again. This is an
undesired effect and can lead to misbehaving directives, performance issues, and memory
leaks.
A common scenario where this happens is a directive that calls `$compile` in a directive link
function on the directive element. In the following **faulty example**, a directive adds a mouseover behavior
to a button with `ngClick` on it:
```
angular.module('app').directive('addMouseover', function($compile) {
return {
link: function(scope, element, attrs) {
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
element.on('mouseenter mouseleave', function() {
scope.$apply('showHint = !showHint');
});
attrs.$set('addMouseover', null); // To stop infinite compile loop
element.append(newEl);
$compile(element)(scope); // Double compilation
}
}
})
```
At first glance, it looks like removing the original `addMouseover` attribute is all there is needed
to make this example work.
However, if the directive element or its children have other directives attached, they will be compiled and
linked again, because the compiler doesn't keep track of which directives have been assigned to which
elements.
This can cause unpredictable behavior, e.g. `ngClick` or other event handlers will be attached
again. It can also degrade performance, as watchers for text interpolation are added twice to the scope.
Double compilation should therefore be avoided. In the above example, only the new element should
be compiled:
```
angular.module('app').directive('addMouseover', function($compile) {
return {
link: function(scope, element, attrs) {
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
element.on('mouseenter mouseleave', function() {
scope.$apply('showHint = !showHint');
});
element.append(newEl);
$compile(newEl)(scope); // Only compile the new element
}
}
})
```
Another scenario is adding a directive programmatically to a compiled element and then executing
compile again. See the following **faulty example**:
```html
<input ng-model="$ctrl.value" add-options>
```
```
angular.module('app').directive('addOptions', function($compile) {
return {
link: function(scope, element, attrs) {
attrs.$set('addOptions', null) // To stop infinite compile loop
attrs.$set('ngModelOptions', '{debounce: 1000}');
$compile(element)(scope); // Double compilation
}
}
});
```
In that case, it is necessary to intercept the *initial* compilation of the element:
1. Give your directive the `terminal` property and a higher priority than directives
that should not be compiled twice. In the example, the compiler will only compile directives
which have a priority of 100 or higher.
2. Inside this directive's compile function, add any other directive attributes to the template.
3. Compile the element, but restrict the maximum priority, so that any already compiled directives
(including the `addOptions` directive) are not compiled again.
4. In the link function, link the compiled element with the element's scope.
```
angular.module('app').directive('addOptions', function($compile) {
return {
priority: 100, // ngModel has priority 1
terminal: true,
compile: function(templateElement, templateAttributes) {
templateAttributes.$set('ngModelOptions', '{debounce: 1000}');
// The third argument is the max priority. Only directives with priority < 100 will be compiled,
// therefore we don't need to remove the attribute
var compiled = $compile(templateElement, null, 100);
return function linkFn(scope) {
compiled(scope) // Link compiled element to scope
}
}
}
});
```
+8 -9
View File
@@ -8,7 +8,8 @@
<div class="alert alert-warning">
**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics.
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
If you're looking for the **directives API**, we recently moved it to {@link ng.$compile `$compile`}.
If you're looking for the **directives API**, you can find it in the
{@link ng.$compile `$compile` API docs}.
</div>
@@ -58,7 +59,7 @@ The following `<input>` element also **matches** `ngModel`:
<input data-ng-model="foo">
```
And the following <person> element **matches** the `person` directive:
And the following `<person>` element **matches** the `person` directive:
```html
<person>{{name}}</person>
@@ -335,9 +336,7 @@ Let's change our directive to use `restrict: 'E'`:
</file>
</example>
For more on the
{@link ng.$compile#directive-definition-object `restrict`}
property, see the
For more on the `restrict` property, see the
{@link ng.$compile#directive-definition-object API docs}.
<div class="alert alert-info">
@@ -450,8 +449,8 @@ scope: {
The **scope option** is an object that contains a property for each isolate scope binding. In this
case it has just one property:
- Its name (`customerInfo`) corresponds to the
directive's **isolate scope** property `customerInfo`.
- Its name (`customerInfo`) corresponds to the directive's **isolate scope** property,
`customerInfo`.
- Its value (`=info`) tells `$compile` to bind to the `info` attribute.
<div class="alert alert-warning">
@@ -517,8 +516,8 @@ that you explicitly pass in.
<div class="alert alert-warning">
**Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not.
See the {@link $compile#directive-definition-object
"Directive Definition Object - scope"} section for more information about isolate scopes.
See the {@link $compile#directive-definition-object "Directive Definition Object - scope"} section
for more information about isolate scopes.
</div>
<div class="alert alert-success">
+7 -6
View File
@@ -117,16 +117,17 @@ You can find a larger list of Angular external libraries at [ngmodules.org](http
## General Learning Resources
### Books
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
* [AngularJS Essentials (Free eBook)](https://www.packtpub.com/packt/free-ebook/angularjs-essentials) by Rodrigo Branas
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
### Videos:
* [egghead.io](http://egghead.io/)
+15 -5
View File
@@ -169,6 +169,7 @@ var
angularModule,
uid = 0;
// Support: IE 9-11 only
/**
* documentMode is an IE-only property
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
@@ -1279,6 +1280,7 @@ function fromJson(json) {
var ALL_COLONS = /:/g;
function timezoneToOffset(timezone, fallback) {
// Support: IE 9-11 only, Edge 13-14+
// IE/Edge do not "understand" colon (`:`) in timezone
timezone = timezone.replace(ALL_COLONS, '');
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
@@ -1453,12 +1455,20 @@ function allowAutoBootstrap(document) {
link.href = src;
var scriptProtocol = link.protocol;
var docLoadProtocol = document.location.protocol;
if ((scriptProtocol === 'resource:' ||
scriptProtocol === 'chrome-extension:') &&
docLoadProtocol !== scriptProtocol) {
return false;
if (docLoadProtocol === scriptProtocol) {
return true;
}
switch (scriptProtocol) {
case 'http:':
case 'https:':
case 'ftp:':
case 'blob:':
case 'file:':
case 'data:':
return true;
default:
return false;
}
return true;
}
// Cached as it has to run during loading so that document.currentScript is available.
+2 -1
View File
@@ -849,8 +849,9 @@ function createInjector(modulesToLoad, strictDi) {
}
function isClass(func) {
// Support: IE 9-11 only
// IE 9-11 do not support classes and IE9 leaks with the code below.
if (msie <= 11 || typeof func !== 'function') {
if (msie || typeof func !== 'function') {
return false;
}
var result = func.$$ngIsClass;
+11
View File
@@ -943,6 +943,16 @@
*
* For information on how the compiler works, see the
* {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
*
* @knownIssue
*
* ### Double Compilation
*
Double compilation occurs when an already compiled part of the DOM gets
compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
section on double compilation} for an in-depth explanation and ways to avoid it.
*
*/
var $compileMinErr = minErr('$compile');
@@ -1949,6 +1959,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
for (var i = 0; i < nodeList.length; i++) {
attrs = new Attributes();
// Support: IE 11 only
// Workaround for #11781 and #14924
if (msie === 11) {
mergeConsecutiveTextNodes(nodeList, i, notLiveList);
+2 -1
View File
@@ -61,7 +61,8 @@ function $ControllerProvider() {
* @description If called, allows `$controller` to find controller constructors on `window`
*
* @deprecated
* removeVersion="v1.7"
* sinceVersion="v1.3.0"
* removeVersion="v1.7.0"
* This method of finding controllers has been deprecated.
*/
this.allowGlobals = function() {
+3 -2
View File
@@ -426,10 +426,11 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
attr.$set(name, value);
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// Support: IE 9-11 only
// On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
// to set the property as well to achieve the desired effect.
// we use attr[attrName] value since $set can sanitize the url.
// We use attr[attrName] value since $set can sanitize the url.
if (msie && propName) element.prop(propName, attr[name]);
});
}
+2 -2
View File
@@ -115,8 +115,8 @@ var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).then(function() {
previousElements = null;
$animate.leave(previousElements).done(function(response) {
if (response !== false) previousElements = null;
});
block = null;
}
+7 -6
View File
@@ -214,8 +214,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
currentScope = null;
}
if (currentElement) {
$animate.leave(currentElement).then(function() {
previousElement = null;
$animate.leave(currentElement).done(function(response) {
if (response !== false) previousElement = null;
});
previousElement = currentElement;
currentElement = null;
@@ -223,9 +223,10 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
};
scope.$watch(srcExp, function ngIncludeWatchAction(src) {
var afterAnimation = function() {
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
var afterAnimation = function(response) {
if (response !== false && isDefined(autoScrollExp) &&
(!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
};
var thisChangeId = ++changeCounter;
@@ -248,7 +249,7 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
// directives to non existing elements.
var clone = $transclude(newScope, function(clone) {
cleanupLastIncludeContent();
$animate.enter(clone, null, $element).then(afterAnimation);
$animate.enter(clone, null, $element).done(afterAnimation);
});
currentScope = newScope;
+9 -6
View File
@@ -153,21 +153,24 @@ var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) {
selectedScopes = [];
var spliceFactory = function(array, index) {
return function() { array.splice(index, 1); };
return function(response) {
if (response !== false) array.splice(index, 1);
};
};
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
var i, ii;
for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
$animate.cancel(previousLeaveAnimations[i]);
// Start with the last, in case the array is modified during the loop
while (previousLeaveAnimations.length) {
$animate.cancel(previousLeaveAnimations.pop());
}
previousLeaveAnimations.length = 0;
for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
var selected = getBlockNodes(selectedElements[i].clone);
selectedScopes[i].$destroy();
var promise = previousLeaveAnimations[i] = $animate.leave(selected);
promise.then(spliceFactory(previousLeaveAnimations, i));
var runner = previousLeaveAnimations[i] = $animate.leave(selected);
runner.done(spliceFactory(previousLeaveAnimations, i));
}
selectedElements.length = 0;
+1
View File
@@ -105,6 +105,7 @@ function $RootScopeProvider() {
function cleanUpScope($scope) {
// Support: IE 9 only
if (msie === 9) {
// There is a memory leak in IE9 if all child scopes are not disconnected
// completely when a scope is destroyed. So this code will recurse up through
+3 -2
View File
@@ -47,8 +47,8 @@ function adjustMatcher(matcher) {
'Illegal sequence *** in string matcher. String: {0}', matcher);
}
matcher = escapeForRegexp(matcher).
replace('\\*\\*', '.*').
replace('\\*', '[^:/.?&;]*');
replace(/\\\*\\\*/g, '.*').
replace(/\\\*/g, '[^:/.?&;]*');
return new RegExp('^' + matcher + '$');
} else if (isRegExp(matcher)) {
// The only other type of matcher allowed is a Regexp.
@@ -748,6 +748,7 @@ function $SceProvider() {
this.$get = ['$parse', '$sceDelegate', function(
$parse, $sceDelegate) {
// Support: IE 9-11 only
// Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
// the "expression(javascript expression)" syntax which is insecure.
if (enabled && msie < 8) {
+2 -1
View File
@@ -57,12 +57,13 @@ function $SnifferProvider() {
// We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
history: !!(hasHistoryPushState && !(android < 4) && !boxee),
hasEvent: function(event) {
// Support: IE 9-11 only
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
// IE10+ implements 'input' event but it erroneously fires under various situations,
// e.g. when placeholder changes, or a form is focused.
if (event === 'input' && msie <= 11) return false;
if (event === 'input' && msie) return false;
if (isUndefined(eventSupport[event])) {
var divElm = document.createElement('div');
+1
View File
@@ -58,6 +58,7 @@ var originUrl = urlResolve(window.location.href);
function urlResolve(url) {
var href = url;
// Support: IE 9-11 only
if (msie) {
// Normalize before parse. Refer Implementation Notes on why this is
// done in two steps on IE.
+1
View File
@@ -5,6 +5,7 @@ angular.module('ngCookies').
* @ngdoc service
* @name $cookieStore
* @deprecated
* sinceVersion="v1.4.0"
* Please use the {@link ngCookies.$cookies `$cookies`} service instead.
*
* @requires $cookies
+4 -4
View File
@@ -207,8 +207,8 @@ function ngViewFactory($route, $anchorScroll, $animate) {
}
if (currentElement) {
previousLeaveAnimation = $animate.leave(currentElement);
previousLeaveAnimation.then(function() {
previousLeaveAnimation = null;
previousLeaveAnimation.done(function(response) {
if (response !== false) previousLeaveAnimation = null;
});
currentElement = null;
}
@@ -229,8 +229,8 @@ function ngViewFactory($route, $anchorScroll, $animate) {
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() {
if (angular.isDefined(autoScrollExp)
$animate.enter(clone, null, currentElement || $element).done(function onNgViewEnter(response) {
if (response !== false && angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
+7
View File
@@ -201,6 +201,13 @@ angular.scenario.dsl('binding', function() {
*/
angular.scenario.dsl('input', function() {
var chain = {};
// Support: IE 9-11 only
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
// IE10+ implements 'input' event but it erroneously fires under various situations,
// e.g. when placeholder changes, or a form is focused.
var supportInputEvent = 'oninput' in window.document.createElement('div') && !msie;
chain.enter = function(value, event) {
+2 -1
View File
@@ -8,7 +8,8 @@
* @ngdoc directive
* @name ngClick
* @deprecated
* Beginning with Angular 1.5, this directive is deprecated and by default **disabled**.
* sinceVersion="v1.5.0"
* This directive is deprecated and **disabled** by default.
* The directive will receive no further support and might be removed from future releases.
* If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled}
* function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick).
+5
View File
@@ -357,8 +357,11 @@ beforeEach(function() {
toBeMarkedAsSelected: function() {
// Selected is special because the element property and attribute reflect each other's state.
// Support: IE 9 only
// IE9 will wrongly report hasAttribute('selected') === true when the property is
// undefined or null, and the dev tools show that no attribute is set
return {
compare: function(actual) {
var errors = [];
@@ -366,6 +369,7 @@ beforeEach(function() {
errors.push('Expected option property "selected" to be truthy');
}
// Support: IE 9 only
if (msie !== 9 && actual.hasAttribute('selected') === false) {
errors.push('Expected option to have attribute "selected"');
}
@@ -383,6 +387,7 @@ beforeEach(function() {
errors.push('Expected option property "selected" to be falsy');
}
// Support: IE 9 only
if (msie !== 9 && actual.hasAttribute('selected')) {
errors.push('Expected option not to have attribute "selected"');
}
+13 -3
View File
@@ -469,6 +469,10 @@ describe('$compile', function() {
}));
// NOTE: This test may be redundant.
// Support: Edge 14+
// An `<svg>` element inside a `<foreignObject>` element on MS Edge has no
// size, causing the included `<circle>` element to also have no size and thus fails an
// assertion (relying on the element having a non-zero size).
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() {
@@ -1127,7 +1131,8 @@ describe('$compile', function() {
expect(element).toHaveClass('class_2');
}));
if (!msie || msie > 11) {
// Support: IE 9-11 only
if (!msie) {
// style interpolation not working on IE (including IE11).
it('should handle interpolated css style from replacing directive', inject(
function($compile, $rootScope) {
@@ -10698,6 +10703,8 @@ describe('$compile', function() {
expect(element.text()).toBe('102030');
expect(newWatcherCount).toBe(3);
// Support: IE 11 only
// See #11781 and #14924
if (msie === 11) {
expect(element.find('ng-transclude').contents().length).toBe(1);
}
@@ -10719,9 +10726,10 @@ describe('$compile', function() {
expect(element.attr('src')).toEqual('http://example.com/image2.png');
}));
// Support: IE 9 only
// IE9 rejects the video / audio tag with "Error: Not implemented" and the source tag with
// "Unable to get value of the property 'childNodes': object is null or undefined"
if (!msie || msie > 9) {
if (msie !== 9) {
they('should NOT require trusted values for $prop src', ['video', 'audio'],
function(tag) {
inject(function($rootScope, $compile, $sce) {
@@ -11154,7 +11162,9 @@ describe('$compile', function() {
}));
});
if (!msie || msie >= 11) {
// Support: IE 9-10 only
// IEs <11 don't support srcdoc
if (!msie || msie === 11) {
describe('iframe[srcdoc]', function() {
it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
+4 -2
View File
@@ -182,6 +182,7 @@ describe('ngSrc', function() {
}));
// Support: IE 9-11 only
if (msie) {
it('should update the element property as well as the attribute', inject(
function($compile, $rootScope, $sce) {
@@ -283,8 +284,9 @@ describe('ngHref', function() {
expect(element.attr('href')).toEqual(undefined);
}));
if (msie) {
// IE11/10/Edge fail when setting a href to a URL containing a % that isn't a valid escape sequence
// Support: IE 9-11 only, Edge 12-14+
if (msie || /\bEdge\/[\d\.]+\b/.test(window.navigator.userAgent)) {
// IE/Edge fail when setting a href to a URL containing a % that isn't a valid escape sequence
// See https://github.com/angular/angular.js/issues/13388
it('should throw error if ng-href contains a non-escaped percent symbol', inject(function($rootScope, $compile) {
element = $compile('<a ng-href="http://www.google.com/{{\'a%link\'}}">')($rootScope);
+1
View File
@@ -135,6 +135,7 @@ describe('input', function() {
describe('IE placeholder input events', function() {
// Support: IE 9-11 only
//IE fires an input event whenever a placeholder visually changes, essentially treating it as a value
//Events:
// placeholder attribute change: *input*
+364 -342
View File
@@ -1,371 +1,393 @@
'use strict';
describe('ngIf', function() {
var $scope, $compile, element, $compileProvider;
beforeEach(module(function(_$compileProvider_) {
$compileProvider = _$compileProvider_;
}));
beforeEach(inject(function($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
element = $compile('<div></div>')($scope);
}));
describe('basic', function() {
var $scope, $compile, element, $compileProvider;
afterEach(function() {
dealoc(element);
});
beforeEach(module(function(_$compileProvider_) {
$compileProvider = _$compileProvider_;
}));
beforeEach(inject(function($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
element = $compile('<div></div>')($scope);
}));
function makeIf() {
forEach(arguments, function(expr) {
element.append($compile('<div class="my-class" ng-if="' + expr + '"><div>Hi</div></div>')($scope));
});
$scope.$apply();
}
it('should immediately remove the element if condition is falsy', function() {
makeIf('false', 'undefined', 'null', 'NaN', '\'\'', '0');
expect(element.children().length).toBe(0);
});
it('should leave the element if condition is true', function() {
makeIf('true');
expect(element.children().length).toBe(1);
});
it('should leave the element if the condition is a non-empty string', function() {
makeIf('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\'');
expect(element.children().length).toBe(6);
});
it('should leave the element if the condition is an object', function() {
makeIf('[]', '{}');
expect(element.children().length).toBe(2);
});
it('should not add the element twice if the condition goes from true to true', function() {
$scope.hello = 'true1';
makeIf('hello');
expect(element.children().length).toBe(1);
$scope.$apply('hello = "true2"');
expect(element.children().length).toBe(1);
});
it('should not recreate the element if the condition goes from true to true', function() {
$scope.hello = 'true1';
makeIf('hello');
element.children().data('flag', true);
$scope.$apply('hello = "true2"');
expect(element.children().data('flag')).toBe(true);
});
it('should create then remove the element if condition changes', function() {
$scope.hello = true;
makeIf('hello');
expect(element.children().length).toBe(1);
$scope.$apply('hello = false');
expect(element.children().length).toBe(0);
});
it('should create a new scope every time the expression evaluates to true', function() {
$scope.$apply('value = true');
element.append($compile(
'<div ng-if="value"><span ng-init="value=false"></span></div>'
)($scope));
$scope.$apply();
expect(element.children('div').length).toBe(1);
});
it('should destroy the child scope every time the expression evaluates to false', function() {
$scope.value = true;
element.append($compile(
'<div ng-if="value"></div>'
)($scope));
$scope.$apply();
var childScope = element.children().scope();
var destroyed = false;
childScope.$on('$destroy', function() {
destroyed = true;
});
$scope.value = false;
$scope.$apply();
expect(destroyed).toBe(true);
});
it('should play nice with other elements beside it', function() {
$scope.values = [1, 2, 3, 4];
element.append($compile(
'<div ng-repeat="i in values"></div>' +
'<div ng-if="values.length==4"></div>' +
'<div ng-repeat="i in values"></div>'
)($scope));
$scope.$apply();
expect(element.children().length).toBe(9);
$scope.$apply('values.splice(0,1)');
expect(element.children().length).toBe(6);
$scope.$apply('values.push(1)');
expect(element.children().length).toBe(9);
});
it('should play nice with ngInclude on the same element', inject(function($templateCache) {
$templateCache.put('test.html', [200, '{{value}}', {}]);
$scope.value = 'first';
element.append($compile(
'<div ng-if="value==\'first\'" ng-include="\'test.html\'"></div>'
)($scope));
$scope.$apply();
expect(element.text()).toBe('first');
$scope.value = 'later';
$scope.$apply();
expect(element.text()).toBe('');
}));
it('should work with multiple elements', function() {
$scope.show = true;
$scope.things = [1, 2, 3];
element.append($compile(
'<div>before;</div>' +
'<div ng-if-start="show">start;</div>' +
'<div ng-repeat="thing in things">{{thing}};</div>' +
'<div ng-if-end>end;</div>' +
'<div>after;</div>'
)($scope));
$scope.$apply();
expect(element.text()).toBe('before;start;1;2;3;end;after;');
$scope.things.push(4);
$scope.$apply();
expect(element.text()).toBe('before;start;1;2;3;4;end;after;');
$scope.show = false;
$scope.$apply();
expect(element.text()).toBe('before;after;');
});
it('should restore the element to its compiled state', function() {
$scope.value = true;
makeIf('value');
expect(element.children().length).toBe(1);
jqLite(element.children()[0]).removeClass('my-class');
expect(element.children()[0].className).not.toContain('my-class');
$scope.$apply('value = false');
expect(element.children().length).toBe(0);
$scope.$apply('value = true');
expect(element.children().length).toBe(1);
expect(element.children()[0].className).toContain('my-class');
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
templateUrl: 'test.html'
};
});
$httpBackend.whenGET('test.html').respond('hello');
element.append('<div ng-if="show" test></div>');
$compile(element)($rootScope);
$rootScope.show = true;
$rootScope.$apply();
expect(element.text()).toBe('');
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.show = false;
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
});
describe('ngIf and transcludes', function() {
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module(function($compileProvider) {
var directive = $compileProvider.directive;
directive('template', valueFn({
template: '<div ng-if="true"><span test></span></div>',
replace: true,
controller: function() {
this.flag = true;
}
}));
directive('test', valueFn({
require: '^template',
link: function(scope, el, attr, ctrl) {
controller = ctrl;
}
}));
});
inject(function($compile, $rootScope) {
var element = $compile('<div><div template></div></div>')($rootScope);
$rootScope.$apply();
expect(controller.flag).toBe(true);
afterEach(function() {
dealoc(element);
});
});
it('should use the correct transcluded scope', function() {
module(function($compileProvider) {
$compileProvider.directive('iso', valueFn({
link: function(scope) {
scope.val = 'value in iso scope';
},
restrict: 'E',
transclude: true,
template: '<div ng-if="true">val={{val}}-<div ng-transclude></div></div>',
scope: {}
}));
});
inject(function($compile, $rootScope) {
$rootScope.val = 'transcluded content';
var element = $compile('<iso><span ng-bind="val"></span></iso>')($rootScope);
$rootScope.$digest();
expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content');
dealoc(element);
});
});
});
describe('ngIf animations', function() {
var body, element, $rootElement;
function html(content) {
$rootElement.html(content);
element = $rootElement.children().eq(0);
return element;
}
beforeEach(module('ngAnimateMock'));
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
$rootElement = _$rootElement_;
body = jqLite(window.document.body);
body.append($rootElement);
};
}));
afterEach(function() {
dealoc(body);
dealoc(element);
});
beforeEach(module(function($animateProvider, $provide) {
return function($animate) {
$animate.enabled(true);
};
}));
it('should fire off the enter animation',
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$rootScope.$digest();
$scope.$apply('value = true');
item = $animate.queue.shift();
expect(item.event).toBe('enter');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(1);
})
);
it('should fire off the leave animation',
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$scope.$apply('value = true');
item = $animate.queue.shift();
expect(item.event).toBe('enter');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(1);
$scope.$apply('value = false');
item = $animate.queue.shift();
expect(item.event).toBe('leave');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(0);
})
);
it('should destroy the previous leave animation if a new one takes place', function() {
module(function($provide) {
$provide.decorator('$animate', function($delegate, $$q) {
var emptyPromise = $$q.defer().promise;
$delegate.leave = function() {
return emptyPromise;
};
return $delegate;
function makeIf() {
forEach(arguments, function(expr) {
element.append($compile('<div class="my-class" ng-if="' + expr + '"><div>Hi</div></div>')($scope));
});
$scope.$apply();
}
it('should immediately remove the element if condition is falsy', function() {
makeIf('false', 'undefined', 'null', 'NaN', '\'\'', '0');
expect(element.children().length).toBe(0);
});
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value">Yo</div>' +
'</div>'
))($scope);
it('should leave the element if condition is true', function() {
makeIf('true');
expect(element.children().length).toBe(1);
});
it('should leave the element if the condition is a non-empty string', function() {
makeIf('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\'');
expect(element.children().length).toBe(6);
});
it('should leave the element if the condition is an object', function() {
makeIf('[]', '{}');
expect(element.children().length).toBe(2);
});
it('should not add the element twice if the condition goes from true to true', function() {
$scope.hello = 'true1';
makeIf('hello');
expect(element.children().length).toBe(1);
$scope.$apply('hello = "true2"');
expect(element.children().length).toBe(1);
});
it('should not recreate the element if the condition goes from true to true', function() {
$scope.hello = 'true1';
makeIf('hello');
element.children().data('flag', true);
$scope.$apply('hello = "true2"');
expect(element.children().data('flag')).toBe(true);
});
it('should create then remove the element if condition changes', function() {
$scope.hello = true;
makeIf('hello');
expect(element.children().length).toBe(1);
$scope.$apply('hello = false');
expect(element.children().length).toBe(0);
});
it('should create a new scope every time the expression evaluates to true', function() {
$scope.$apply('value = true');
element.append($compile(
'<div ng-if="value"><span ng-init="value=false"></span></div>'
)($scope));
$scope.$apply();
expect(element.children('div').length).toBe(1);
});
var destroyed, inner = element.children(0);
inner.on('$destroy', function() {
it('should destroy the child scope every time the expression evaluates to false', function() {
$scope.value = true;
element.append($compile(
'<div ng-if="value"></div>'
)($scope));
$scope.$apply();
var childScope = element.children().scope();
var destroyed = false;
childScope.$on('$destroy', function() {
destroyed = true;
});
$scope.$apply('value = false');
$scope.$apply('value = true');
$scope.$apply('value = false');
$scope.value = false;
$scope.$apply();
expect(destroyed).toBe(true);
});
});
it('should work with svg elements when the svg container is transcluded', function() {
module(function($compileProvider) {
$compileProvider.directive('svgContainer', function() {
it('should play nice with other elements beside it', function() {
$scope.values = [1, 2, 3, 4];
element.append($compile(
'<div ng-repeat="i in values"></div>' +
'<div ng-if="values.length==4"></div>' +
'<div ng-repeat="i in values"></div>'
)($scope));
$scope.$apply();
expect(element.children().length).toBe(9);
$scope.$apply('values.splice(0,1)');
expect(element.children().length).toBe(6);
$scope.$apply('values.push(1)');
expect(element.children().length).toBe(9);
});
it('should play nice with ngInclude on the same element', inject(function($templateCache) {
$templateCache.put('test.html', [200, '{{value}}', {}]);
$scope.value = 'first';
element.append($compile(
'<div ng-if="value==\'first\'" ng-include="\'test.html\'"></div>'
)($scope));
$scope.$apply();
expect(element.text()).toBe('first');
$scope.value = 'later';
$scope.$apply();
expect(element.text()).toBe('');
}));
it('should work with multiple elements', function() {
$scope.show = true;
$scope.things = [1, 2, 3];
element.append($compile(
'<div>before;</div>' +
'<div ng-if-start="show">start;</div>' +
'<div ng-repeat="thing in things">{{thing}};</div>' +
'<div ng-if-end>end;</div>' +
'<div>after;</div>'
)($scope));
$scope.$apply();
expect(element.text()).toBe('before;start;1;2;3;end;after;');
$scope.things.push(4);
$scope.$apply();
expect(element.text()).toBe('before;start;1;2;3;4;end;after;');
$scope.show = false;
$scope.$apply();
expect(element.text()).toBe('before;after;');
});
it('should restore the element to its compiled state', function() {
$scope.value = true;
makeIf('value');
expect(element.children().length).toBe(1);
jqLite(element.children()[0]).removeClass('my-class');
expect(element.children()[0].className).not.toContain('my-class');
$scope.$apply('value = false');
expect(element.children().length).toBe(0);
$scope.$apply('value = true');
expect(element.children().length).toBe(1);
expect(element.children()[0].className).toContain('my-class');
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
template: '<svg ng-transclude></svg>',
replace: true,
transclude: true
templateUrl: 'test.html'
};
});
});
inject(function($compile, $rootScope) {
element = $compile('<svg-container><circle ng-if="flag"></circle></svg-container>')($rootScope);
$rootScope.flag = true;
$httpBackend.whenGET('test.html').respond('hello');
element.append('<div ng-if="show" test></div>');
$compile(element)($rootScope);
$rootScope.show = true;
$rootScope.$apply();
expect(element.text()).toBe('');
var circle = element.find('circle');
expect(circle[0].toString()).toMatch(/SVG/);
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.show = false;
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
it('should not trigger a digest when the element is removed', inject(function($$rAF, $rootScope, $timeout) {
var spy = spyOn($rootScope, '$digest').and.callThrough();
$scope.hello = true;
makeIf('hello');
expect(element.children().length).toBe(1);
$scope.$apply('hello = false');
spy.calls.reset();
expect(element.children().length).toBe(0);
// The animation completion is async even without actual animations
$$rAF.flush();
expect(spy).not.toHaveBeenCalled();
// A digest may have been triggered asynchronously, so check the queue
$timeout.verifyNoPendingTasks();
}));
});
describe('and transcludes', function() {
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module(function($compileProvider) {
var directive = $compileProvider.directive;
directive('template', valueFn({
template: '<div ng-if="true"><span test></span></div>',
replace: true,
controller: function() {
this.flag = true;
}
}));
directive('test', valueFn({
require: '^template',
link: function(scope, el, attr, ctrl) {
controller = ctrl;
}
}));
});
inject(function($compile, $rootScope) {
var element = $compile('<div><div template></div></div>')($rootScope);
$rootScope.$apply();
expect(controller.flag).toBe(true);
dealoc(element);
});
});
it('should use the correct transcluded scope', function() {
module(function($compileProvider) {
$compileProvider.directive('iso', valueFn({
link: function(scope) {
scope.val = 'value in iso scope';
},
restrict: 'E',
transclude: true,
template: '<div ng-if="true">val={{val}}-<div ng-transclude></div></div>',
scope: {}
}));
});
inject(function($compile, $rootScope) {
$rootScope.val = 'transcluded content';
var element = $compile('<iso><span ng-bind="val"></span></iso>')($rootScope);
$rootScope.$digest();
expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content');
dealoc(element);
});
});
});
describe('and animations', function() {
var body, element, $rootElement;
function html(content) {
$rootElement.html(content);
element = $rootElement.children().eq(0);
return element;
}
beforeEach(module('ngAnimateMock'));
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
$rootElement = _$rootElement_;
body = jqLite(window.document.body);
body.append($rootElement);
};
}));
afterEach(function() {
dealoc(body);
dealoc(element);
});
beforeEach(module(function($animateProvider, $provide) {
return function($animate) {
$animate.enabled(true);
};
}));
it('should fire off the enter animation',
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$rootScope.$digest();
$scope.$apply('value = true');
item = $animate.queue.shift();
expect(item.event).toBe('enter');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(1);
})
);
it('should fire off the leave animation',
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$scope.$apply('value = true');
item = $animate.queue.shift();
expect(item.event).toBe('enter');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(1);
$scope.$apply('value = false');
item = $animate.queue.shift();
expect(item.event).toBe('leave');
expect(item.element.text()).toBe('Hi');
expect(element.children().length).toBe(0);
})
);
it('should destroy the previous leave animation if a new one takes place', function() {
module(function($provide) {
$provide.decorator('$animate', function($delegate, $$q) {
var emptyPromise = $$q.defer().promise;
emptyPromise.done = noop;
$delegate.leave = function() {
return emptyPromise;
};
return $delegate;
});
});
inject(function($compile, $rootScope, $animate) {
var item;
var $scope = $rootScope.$new();
element = $compile(html(
'<div>' +
'<div ng-if="value">Yo</div>' +
'</div>'
))($scope);
$scope.$apply('value = true');
var destroyed, inner = element.children(0);
inner.on('$destroy', function() {
destroyed = true;
});
$scope.$apply('value = false');
$scope.$apply('value = true');
$scope.$apply('value = false');
expect(destroyed).toBe(true);
});
});
it('should work with svg elements when the svg container is transcluded', function() {
module(function($compileProvider) {
$compileProvider.directive('svgContainer', function() {
return {
template: '<svg ng-transclude></svg>',
replace: true,
transclude: true
};
});
});
inject(function($compile, $rootScope) {
element = $compile('<svg-container><circle ng-if="flag"></circle></svg-container>')($rootScope);
$rootScope.flag = true;
$rootScope.$apply();
var circle = element.find('circle');
expect(circle[0].toString()).toMatch(/SVG/);
});
});
});
});
File diff suppressed because it is too large Load Diff
+71
View File
@@ -54,12 +54,14 @@ describe('ngSwitch', function() {
$rootScope.name = 'shyam';
$rootScope.$apply();
expect(element.text()).toEqual('first:shyam, first too:shyam');
$rootScope.select = 2;
$rootScope.$apply();
expect(element.text()).toEqual('second:shyam');
$rootScope.name = 'misko';
$rootScope.$apply();
expect(element.text()).toEqual('second:misko');
$rootScope.select = true;
$rootScope.$apply();
expect(element.text()).toEqual('true:misko');
@@ -301,7 +303,66 @@ describe('ngSwitch', function() {
}));
it('should not trigger a digest after an element is removed', inject(function($$rAF, $compile, $rootScope, $timeout) {
var spy = spyOn($rootScope, '$digest').and.callThrough();
$rootScope.select = 1;
element = $compile(
'<div ng-switch="select">' +
'<div ng-switch-when="1">first</div>' +
'<div ng-switch-when="2">second</div>' +
'</div>')($rootScope);
$rootScope.$apply();
expect(element.text()).toEqual('first');
$rootScope.select = 2;
$rootScope.$apply();
spy.calls.reset();
expect(element.text()).toEqual('second');
// If ngSwitch re-introduces code that triggers a digest after an element is removed (in an
// animation .then callback), flushing the queue ensures the callback will be called, and the test
// fails
$$rAF.flush();
expect(spy).not.toHaveBeenCalled();
// A digest may have been triggered asynchronously, so check the queue
$timeout.verifyNoPendingTasks();
}));
it('should handle changes to the switch value in a digest loop with multiple value matches',
inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
scope.value = 'foo';
scope.$watch('value', function() {
if (scope.value === 'bar') {
scope.$evalAsync(function() {
scope.value = 'baz';
});
}
});
element = $compile(
'<div ng-switch="value">' +
'<div ng-switch-when="foo">FOO 1</div>' +
'<div ng-switch-when="foo">FOO 2</div>' +
'<div ng-switch-when="bar">BAR</div>' +
'<div ng-switch-when="baz">BAZ</div>' +
'</div>')(scope);
scope.$apply();
expect(element.text()).toBe('FOO 1FOO 2');
scope.$apply('value = "bar"');
expect(element.text()).toBe('BAZ');
})
);
describe('ngSwitchWhen separator', function() {
it('should be possible to define a separator', inject(function($rootScope, $compile) {
element = $compile(
'<div ng-switch="mode">' +
@@ -315,9 +376,11 @@ describe('ngSwitch', function() {
expect(element.children().length).toBe(2);
expect(element.text()).toBe('Block1|Block2|');
$rootScope.$apply('mode = "b"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block1|');
$rootScope.$apply('mode = "c"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block3|');
}));
@@ -336,9 +399,11 @@ describe('ngSwitch', function() {
expect(element.children().length).toBe(2);
expect(element.text()).toBe('Block1|Block2|');
$rootScope.$apply('mode = ""');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block1|');
$rootScope.$apply('mode = "c"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block3|');
}));
@@ -357,9 +422,11 @@ describe('ngSwitch', function() {
expect(element.children().length).toBe(2);
expect(element.text()).toBe('Block1|Block2|');
$rootScope.$apply('mode = "b"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block1|');
$rootScope.$apply('mode = "c"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block3|');
}));
@@ -378,9 +445,11 @@ describe('ngSwitch', function() {
expect(element.children().length).toBe(2);
expect(element.text()).toBe('Block1|Block2|');
$rootScope.$apply('mode = "b|a"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block1|');
$rootScope.$apply('mode = "c"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block3|');
}));
@@ -399,9 +468,11 @@ describe('ngSwitch', function() {
expect(element.children().length).toBe(2);
expect(element.text()).toBe('Block1|Block2|');
$rootScope.$apply('mode = "b"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block1|');
$rootScope.$apply('mode = "c"');
expect(element.children().length).toBe(1);
expect(element.text()).toBe('Block3|');
}));
+10 -6
View File
@@ -39,8 +39,11 @@ describe('$location', function() {
/* global urlParsingNode: true */
var urlParsingNodePlaceholder;
beforeEach(inject(function($sniffer) {
if (msie) return;
beforeEach(function() {
// Support: non-Windows browsers
// These tests expect a Windows environment which we can only guarantee
// on IE & Edge.
if (msie || /\bEdge\/[\d\.]+\b/.test(window.navigator.userAgent)) return;
urlParsingNodePlaceholder = urlParsingNode;
@@ -57,13 +60,14 @@ describe('$location', function() {
search: '',
setAttribute: angular.noop
};
}));
});
afterEach(inject(function($sniffer) {
if (msie) return;
afterEach(function() {
// Support: non-Windows browsers
if (msie || /\bEdge\/[\d\.]+\b/.test(window.navigator.userAgent)) return;
//reset urlParsingNode
urlParsingNode = urlParsingNodePlaceholder;
}));
});
it('should not include the drive name in path() on WIN', function() {
+2 -1
View File
@@ -2180,8 +2180,9 @@ describe('parser', function() {
expect(scope.$eval('getter()()')).toBe(33);
});
// Support: IE 9 only
// There is no "strict mode" in IE9
if (!msie || msie > 9) {
if (msie !== 9) {
it('should set no context to functions returned by other functions', function() {
scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
scope.$eval('getter()()');
+5 -4
View File
@@ -13,9 +13,7 @@ describe('Scope', function() {
it('should expose the constructor', inject(function($rootScope) {
if (msie < 11) return;
// eslint-disable-next-line no-proto
expect($rootScope.__proto__).toBe($rootScope.constructor.prototype);
expect(Object.getPrototypeOf($rootScope)).toBe($rootScope.constructor.prototype);
}));
@@ -125,7 +123,9 @@ describe('Scope', function() {
function Listener() {
expect(this).toBeUndefined();
}
if (msie < 10) return;
// Support: IE 9 only
// IE 9 doesn't support strict mode so its `this` will always be defined.
if (msie === 9) return;
$rootScope.$watch(Getter, Listener);
$rootScope.$digest();
}));
@@ -1227,6 +1227,7 @@ describe('Scope', function() {
}));
// Support: IE 9 only
if (msie === 9) {
// See issue https://github.com/angular/angular.js/issues/10706
it('should completely disconnect all child scopes on IE9', inject(function($rootScope) {
+4
View File
@@ -334,6 +334,10 @@ describe('SCE', function() {
expect(adjustMatcher(/^a.*b$/).exec('a.b')).not.toBeNull();
expect(adjustMatcher(/^a.*b$/).exec('-a.b-')).toBeNull();
});
it('should should match * and **', function() {
expect(adjustMatcher('*://*.example.com/**').exec('http://www.example.com/path')).not.toBeNull();
});
});
describe('regex matcher', function() {
+2 -1
View File
@@ -153,11 +153,12 @@ describe('$sniffer', function() {
it('should claim that IE9 doesn\'t have support for "oninput"', function() {
// Support: IE 9-11 only
// IE9 implementation is fubared, so it's better to pretend that it doesn't have the support
// IE10+ implementation is fubared when mixed with placeholders
mockDivElement = {oninput: noop};
expect($sniffer.hasEvent('input')).toBe(!(msie && msie <= 11));
expect($sniffer.hasEvent('input')).toBe(!msie);
});
});
+77 -100
View File
@@ -327,8 +327,7 @@ describe('ngAnimate $animateCss', function() {
inject(function($animateCss) {
element.attr('style', '');
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
animator = $animateCss(element, {
event: 'enter',
@@ -348,8 +347,7 @@ describe('ngAnimate $animateCss', function() {
inject(function($animateCss) {
element.attr('style', '');
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
animator = $animateCss(element, {
event: 'enter',
@@ -478,8 +476,8 @@ describe('ngAnimate $animateCss', function() {
});
it('should use the highest transition duration value detected in the CSS class', inject(function($animateCss) {
ss.addRule('.ng-enter', 'transition:1s linear all;' +
'transition-duration:10s, 15s, 20s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-duration:10s, 15s, 20s;');
var animator = $animateCss(element, options);
animator.start();
@@ -497,8 +495,8 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest transition delay value detected in the CSS class', inject(function($animateCss) {
ss.addRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s, 15s, 20s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s, 15s, 20s;');
var animator = $animateCss(element, options);
animator.start();
@@ -518,7 +516,7 @@ describe('ngAnimate $animateCss', function() {
it('should only close when both the animation delay and duration have passed',
inject(function($animateCss) {
ss.addRule('.ng-enter', 'transition:10s 5s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s 5s linear all;');
var animator = $animateCss(element, options);
animator.start();
@@ -535,8 +533,7 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest keyframe duration value detected in the CSS class', inject(function($animateCss) {
ss.addRule('.ng-enter', 'animation:animation 1s, animation 2s, animation 3s;' +
'-webkit-animation:animation 1s, animation 2s, animation 3s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 1s, animation 2s, animation 3s;');
var animator = $animateCss(element, options);
animator.start();
@@ -554,8 +551,7 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest keyframe delay value detected in the CSS class', inject(function($animateCss) {
ss.addRule('.ng-enter', 'animation:animation 1s 2s, animation 1s 10s, animation 1s 1000ms;' +
'-webkit-animation:animation 1s 2s, animation 1s 10s, animation 1s 1000ms;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 1s 2s, animation 1s 10s, animation 1s 1000ms;');
var animator = $animateCss(element, options);
animator.start();
@@ -573,9 +569,8 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest keyframe duration value detected in the CSS class with respect to the animation-iteration-count property', inject(function($animateCss) {
ss.addRule('.ng-enter',
'animation:animation 1s 2s 3, animation 1s 10s 2, animation 1s 1000ms infinite;' +
'-webkit-animation:animation 1s 2s 3, animation 1s 10s 2, animation 1s 1000ms infinite;');
ss.addPossiblyPrefixedRule('.ng-enter',
'animation:animation 1s 2s 3, animation 1s 10s 2, animation 1s 1000ms infinite;');
var animator = $animateCss(element, options);
animator.start();
@@ -593,10 +588,9 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest duration value when both transitions and keyframes are used', inject(function($animateCss) {
ss.addRule('.ng-enter', 'transition:1s linear all;' +
'transition-duration:10s, 15s, 20s;' +
'animation:animation 1s, animation 2s, animation 3s 0s 7;' +
'-webkit-animation:animation 1s, animation 2s, animation 3s 0s 7;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-duration:10s, 15s, 20s;' +
'animation:animation 1s, animation 2s, animation 3s 0s 7;');
var animator = $animateCss(element, options);
animator.start();
@@ -622,10 +616,9 @@ describe('ngAnimate $animateCss', function() {
}));
it('should use the highest delay value when both transitions and keyframes are used', inject(function($animateCss) {
ss.addRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s, 15s, 20s;' +
'animation:animation 1s 2s, animation 1s 16s, animation 1s 19s;' +
'-webkit-animation:animation 1s 2s, animation 1s 16s, animation 1s 19s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s, 15s, 20s;' +
'animation:animation 1s 2s, animation 1s 16s, animation 1s 19s;');
var animator = $animateCss(element, options);
animator.start();
@@ -656,8 +649,8 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all');
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all');
var elements = [];
var i;
@@ -697,13 +690,13 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.red-add-stagger,' +
'.blue-remove-stagger,' +
'.green-add-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.red-add-stagger,' +
'.blue-remove-stagger,' +
'.green-add-stagger', 'transition-delay:0.2s');
ss.addRule('.red-add,' +
'.blue-remove,' +
'.green-add', 'transition:2s linear all');
ss.addPossiblyPrefixedRule('.red-add,' +
'.blue-remove,' +
'.green-add', 'transition:2s linear all');
var elements = [];
var i;
@@ -767,8 +760,8 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all;');
var element;
var i;
@@ -827,7 +820,7 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
ss.addPossiblyPrefixedRule('.transition-animation', 'transition:2s 5s linear all;');
for (var i = 0; i < 5; i++) {
var element = angular.element('<div class="transition-animation"></div>');
@@ -846,8 +839,8 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
ss.addRule('.transition-animation.ng-enter-stagger',
ss.addPossiblyPrefixedRule('.transition-animation', 'transition:2s 5s linear all;');
ss.addPossiblyPrefixedRule('.transition-animation.ng-enter-stagger',
'transition-duration:0s; transition-delay:0.2s;');
var element, i, elms = [];
@@ -892,7 +885,7 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.transition-animation', 'animation: 2s 5s my_animation;');
for (var i = 0; i < 5; i++) {
@@ -950,8 +943,8 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
var elm, i, elms = [];
for (i = 0; i < 5; i++) {
@@ -977,8 +970,8 @@ describe('ngAnimate $animateCss', function() {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');
var elm, i, elms = [];
for (i = 0; i < 5; i++) {
@@ -1003,7 +996,7 @@ describe('ngAnimate $animateCss', function() {
inject(function($animateCss, $document, $rootElement, $timeout) {
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter', 'transition:2s linear all');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all');
var elm, i, elements = [];
for (i = 0; i < 5; i++) {
@@ -1068,7 +1061,7 @@ describe('ngAnimate $animateCss', function() {
it('should close off the animation after 150% of the animation time has passed',
inject(function($animateCss, $document, $rootElement, $timeout) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -1091,7 +1084,7 @@ describe('ngAnimate $animateCss', function() {
it('should close off the animation after 150% of the animation time has passed and consider the detected delay value',
inject(function($animateCss, $document, $rootElement, $timeout) {
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -1114,7 +1107,7 @@ describe('ngAnimate $animateCss', function() {
it('should still resolve the animation once expired',
inject(function($animateCss, $document, $rootElement, $timeout, $animate, $rootScope) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -1139,7 +1132,7 @@ describe('ngAnimate $animateCss', function() {
it('should not resolve/reject after passing if the animation completed successfully',
inject(function($animateCss, $document, $rootElement, $timeout, $rootScope, $animate) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -1184,9 +1177,9 @@ describe('ngAnimate $animateCss', function() {
var cancelSpy = spyOn($timeout, 'cancel').and.callThrough();
var doneSpy = jasmine.createSpy();
ss.addRule('.elm', 'transition:1s linear all;');
ss.addPossiblyPrefixedRule('.elm', 'transition:1s linear all;');
ss.addRule('.elm.red', 'background:red;');
ss.addRule('.elm.blue', 'transition:2s linear all; background:blue;');
ss.addPossiblyPrefixedRule('.elm.blue', 'transition:2s linear all; background:blue;');
ss.addRule('.elm.green', 'background:green;');
var element = angular.element('<div class="elm"></div>');
@@ -1238,7 +1231,7 @@ describe('ngAnimate $animateCss', function() {
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
ss.addRule('.red', 'transition:1s linear all;');
ss.addPossiblyPrefixedRule('.red', 'transition:1s linear all;');
$animateCss(element, { addClass: 'red' }).start();
triggerAnimationStartFrame();
@@ -1319,7 +1312,7 @@ describe('ngAnimate $animateCss', function() {
it('should cancel the timeout when the animation is ended normally',
inject(function($animateCss, $document, $rootElement, $timeout) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -1449,12 +1442,11 @@ describe('ngAnimate $animateCss', function() {
function setStyles(event) {
switch (event) {
case TRANSITIONEND_EVENT:
ss.addRule('.ng-enter', 'transition: 10s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 10s linear all;');
progress = transitionProgress;
break;
case ANIMATIONEND_EVENT:
ss.addRule('.ng-enter', '-webkit-animation: animation 10s;' +
'animation: animation 10s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation: animation 10s;');
progress = keyframeProgress;
break;
}
@@ -1626,8 +1618,7 @@ describe('ngAnimate $animateCss', function() {
expect(animator.$$willAnimate).toBeFalsy();
$$rAF.flush();
ss.addRule('.ng-enter', '-webkit-animation:3.5s keyframe_animation;' +
'animation:3.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3.5s keyframe_animation;');
animator = $animateCss(element, options);
expect(animator.$$willAnimate).toBeTruthy();
}));
@@ -1753,7 +1744,7 @@ describe('ngAnimate $animateCss', function() {
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
var data = {};
@@ -1783,7 +1774,7 @@ describe('ngAnimate $animateCss', function() {
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
var data = {};
@@ -1824,7 +1815,7 @@ describe('ngAnimate $animateCss', function() {
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
var data = {};
@@ -1973,10 +1964,10 @@ describe('ngAnimate $animateCss', function() {
var element = angular.element('<div></div>');
if (event === 'add') {
ss.addRule('.natural-class', 'transition:1s linear all;');
ss.addPossiblyPrefixedRule('.natural-class', 'transition:1s linear all;');
} else {
ss.addRule('.natural-class', 'transition:0s linear none;');
ss.addRule('.base-class', 'transition:1s linear none;');
ss.addPossiblyPrefixedRule('.natural-class', 'transition:0s linear none;');
ss.addPossiblyPrefixedRule('.base-class', 'transition:1s linear none;');
element.addClass('base-class');
element.addClass('natural-class');
@@ -2006,7 +1997,7 @@ describe('ngAnimate $animateCss', function() {
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $document) {
ss.addRule('.blue.ng-' + event, 'transition:2s linear all;');
ss.addPossiblyPrefixedRule('.blue.ng-' + event, 'transition:2s linear all;');
var element = angular.element('<div class="red"></div>');
$rootElement.append(element);
@@ -2169,8 +2160,7 @@ describe('ngAnimate $animateCss', function() {
it('should be applied to a CSS keyframe animation directly if keyframes are detected within the CSS class',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
var options = {
duration: 5,
@@ -2188,8 +2178,7 @@ describe('ngAnimate $animateCss', function() {
it('should remove all inline keyframe styling when an animation completes if a custom duration was applied',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
var options = {
duration: 5,
@@ -2210,8 +2199,7 @@ describe('ngAnimate $animateCss', function() {
it('should remove all inline keyframe delay styling when an animation completes if a custom duration was applied',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
var options = {
delay: 5,
@@ -2234,8 +2222,7 @@ describe('ngAnimate $animateCss', function() {
it('should not prepare the animation at all if a duration of zero is provided',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;');
var options = {
duration: 0,
@@ -2251,9 +2238,8 @@ describe('ngAnimate $animateCss', function() {
it('should apply a transition and keyframe duration directly if both transitions and keyframe classes are detected',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:3s keyframe_animation;' +
'animation:3s keyframe_animation;' +
'transition:5s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3s keyframe_animation;' +
'transition:5s linear all;');
var options = {
duration: 4,
@@ -2314,10 +2300,8 @@ describe('ngAnimate $animateCss', function() {
it('should override the delay value present in the CSS class',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all;' +
'-webkit-transition-delay:10s;' +
'transition-delay:10s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -2339,10 +2323,8 @@ describe('ngAnimate $animateCss', function() {
it('should allow the delay value to zero if provided',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all;' +
'-webkit-transition-delay:10s;' +
'transition-delay:10s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
'transition-delay:10s;');
var element = angular.element('<div></div>');
$rootElement.append(element);
@@ -2364,8 +2346,7 @@ describe('ngAnimate $animateCss', function() {
it('should be applied to a CSS keyframe animation if detected within the CSS class',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
'animation:1.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
var options = {
delay: 400,
@@ -2384,9 +2365,8 @@ describe('ngAnimate $animateCss', function() {
it('should apply a transition and keyframe delay if both transitions and keyframe classes are detected',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:3s keyframe_animation;' +
'animation:3s keyframe_animation;' +
'transition:5s linear all;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3s keyframe_animation;' +
'transition:5s linear all;');
var options = {
delay: 10,
@@ -2419,9 +2399,8 @@ describe('ngAnimate $animateCss', function() {
});
inject(function($animateCss, $rootElement) {
element.addClass('element');
ss.addRule('.element', '-webkit-animation:3s keyframe_animation;' +
'animation:3s keyframe_animation;' +
'transition:5s linear all;');
ss.addPossiblyPrefixedRule('.element', 'animation:3s keyframe_animation;' +
'transition:5s linear all;');
var options = {
delay: 2,
@@ -2455,7 +2434,7 @@ describe('ngAnimate $animateCss', function() {
it('should apply blocking before the animation starts, but then apply the detected delay when options.delay is true',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', 'transition:2s linear all; transition-delay: 1s;');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all; transition-delay: 1s;');
var options = {
delay: true,
@@ -2834,7 +2813,7 @@ describe('ngAnimate $animateCss', function() {
it('should always apply the from styles before the start function is called even if no transition is detected when started',
inject(function($animateCss, $rootElement) {
ss.addRule('.my-class', 'transition: 0s linear color');
ss.addPossiblyPrefixedRule('.my-class', 'transition: 0s linear color');
var options = {
addClass: 'my-class',
@@ -2925,7 +2904,7 @@ describe('ngAnimate $animateCss', function() {
it('should remove all inline transition delay styling when an animation completes',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', 'transition: 1s linear color');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 1s linear color');
var options = {
event: 'enter',
@@ -3000,7 +2979,7 @@ describe('ngAnimate $animateCss', function() {
it('should apply a transition duration if the existing transition duration\'s property value is not \'all\'',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', 'transition: 1s linear color');
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 1s linear color');
var emptyObject = {};
var options = {
@@ -3023,8 +3002,7 @@ describe('ngAnimate $animateCss', function() {
it('should apply a transition duration and an animation duration if duration + styles options are provided for a matching keyframe animation',
inject(function($animateCss, $rootElement) {
ss.addRule('.ng-enter', '-webkit-animation:3.5s keyframe_animation;' +
'animation:3.5s keyframe_animation;');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3.5s keyframe_animation;');
var emptyObject = {};
var options = {
@@ -3056,7 +3034,7 @@ describe('ngAnimate $animateCss', function() {
}));
it('should apply easing to a transition animation if it exists', inject(function($animateCss) {
ss.addRule('.red', 'transition:1s linear all;');
ss.addPossiblyPrefixedRule('.red', 'transition:1s linear all;');
var easing = 'ease-out';
var animator = $animateCss(element, { addClass: 'red', easing: easing });
animator.start();
@@ -3081,7 +3059,7 @@ describe('ngAnimate $animateCss', function() {
it('should apply easing to both keyframes and transition animations if detected',
inject(function($animateCss) {
ss.addRule('.red', 'transition: 1s linear all;');
ss.addPossiblyPrefixedRule('.red', 'transition: 1s linear all;');
ss.addPossiblyPrefixedRule('.blue', 'animation: 1s my_keyframe;');
var easing = 'ease-out';
var animator = $animateCss(element, { addClass: 'red blue', easing: easing });
@@ -3162,8 +3140,7 @@ describe('ngAnimate $animateCss', function() {
it('should round up long elapsedTime values to close off a CSS3 animation',
inject(function($animateCss) {
ss.addRule('.millisecond-transition.ng-leave', '-webkit-transition:510ms linear all;' +
'transition:510ms linear all;');
ss.addPossiblyPrefixedRule('.millisecond-transition.ng-leave', 'transition:510ms linear all;');
element.addClass('millisecond-transition');
var animator = $animateCss(element, {
+34
View File
@@ -584,6 +584,40 @@ describe('ngView', function() {
});
});
});
it('should not trigger a digest when the view is changed', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {templateUrl: 'myUrl1'});
$routeProvider.when('/bar', {templateUrl: 'myUrl2'});
});
inject(function($$rAF, $templateCache, $rootScope, $compile, $timeout, $location, $httpBackend) {
var spy = spyOn($rootScope, '$digest').and.callThrough();
$templateCache.put('myUrl1', 'my template content');
$templateCache.put('myUrl2', 'my other template content');
$location.path('/foo');
$rootScope.$digest();
// The animation completion is async even without actual animations
$$rAF.flush();
expect(element.text()).toEqual('my template content');
$location.path('/bar');
$rootScope.$digest();
spy.calls.reset();
$$rAF.flush();
expect(element.text()).toEqual('my other template content');
expect(spy).not.toHaveBeenCalled();
// A digest may have been triggered asynchronously, so check the queue
$timeout.verifyNoPendingTasks();
});
});
});
describe('and transcludes', function() {