Compare commits

...

25 Commits

Author SHA1 Message Date
Matias Niemelä 2f61145475 chore(CHANGELOG): update with changes for 1.4.7 2015-09-29 13:54:51 -07:00
Martin Staffa 8c618d896b docs($http): link to usage where config is mentioned; make drier
Linking to usage section makes it easier for beginners to find out what the config object looks like.
The General Usage section now features an example that actually uses $http(config), and the Shortcut Methods section has been moved so that it appears directly after.

Closes #12949
Closes #12950
2015-09-27 15:48:20 +02:00
Martin Staffa 68d4dc5b71 fix(ngOptions): skip comments when looking for option elements
When the empty/blank option has a directive that transcludes, ngIf for example,
a comment will be added into the select. Previously, ngOptions used this
comment as the empty option, which would mess up the displayed options.

Closes #12190
2015-09-27 15:48:13 +02:00
Martin Staffa 03a4a96cf9 test(ngOptions): clarify a test description 2015-09-27 15:48:06 +02:00
Stefan Krüger 655c52a621 docs(guide/Directives): let myTabs directive ctrl use inline array notation
modified `docsTabsExample` myTabs directive ctrl at
[Creating Directives that Communicate Example](https://docs.angularjs.org/guide/directive#creating-directives-that-communicate) so that it uses
[Inline Array Annotation](https://docs.angularjs.org/guide/di#inline-array-annotation)
and is compatible with
[Using Strict Dependency Injection](https://docs.angularjs.org/guide/di#using-strict-dependency-injection)

Closes #12767
2015-09-27 15:47:57 +02:00
Martin Staffa fa3ddba5f2 docs(ngModel): align $viewValue description with $setViewValue 2015-09-27 15:47:43 +02:00
Matias Niemelä c4a1b6124e docs($animateCss): options.transition should be options.transitionStyle 2015-09-24 10:06:22 -07:00
Matias Niemelä e52d731bfd feat($animateCss): add support for temporary styles via cleanupStyles
Some animations make use of the `from` and `to` styling only for the
lifetime of the animation. This patch allows for those styles to be
removed once the animation is closed automatically within `$animateCss`.

Closes #12930
2015-09-24 10:02:30 -07:00
Igor Minar 9b72843018 build(travis): make sauce connect process query a bit more specific 2015-09-23 14:01:32 -07:00
Georgios Kalpakas 9c1f8ea70b chore(check-node-modules): make check/reinstall node_modules work across platforms
The previous implementations (based on shell scripts) threw errors on
Windows, because it was not able to `rm -rf` 'node_modules' (due to the
255 character limit in file-paths).

This implementation works consistently across platforms and is heavily based on
'https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js'.

Fixes #11143
Closes #11353

Closes #12792
2015-09-23 23:01:15 +03:00
Igor Minar 9fde5648e4 build(travis): fix typo in a comment 2015-09-23 11:01:00 -07:00
Igor Minar ea829620b2 build(travis): gracefully shut down the sauce connect tunnel after the tests are done running
This is to prevent sauce connect tunnel leaks.

Closes #12921
2015-09-23 09:40:27 -07:00
Martin Staffa 1731d091f8 docs(ngList): whitespace -> newline 2015-09-23 17:38:15 +02:00
Matias Niemelä 9d3704ca46 fix(ngAnimate): ensure anchoring uses body as a container when needed
Prior to this fix anchoring would allow for a container to be a document
node or something higher beyond the body tag. This patch makes it fall
back to body incase the rootElement node exists as a parent ancestor.

Closes #12872
2015-09-22 13:47:16 -07:00
Matias Niemelä 215dff34dd revert: chore(core): introduce $$body service
Relying on the body node to be present right at injection has
caused issues with unit testing as well as some animations on
the body element. Reverting this patch fixes these issues.

Closes #12874
2015-09-22 13:47:10 -07:00
Matias Niemelä fa8c399fad fix(ngAnimate): callback detection should only use RAF when necessary
Callbacks are detected within the internals of ngAnimate whenever an
animation starts and ends. In order to allow the user to set callbacks
the callback detection needs to happen during the next tick. Prior to
this fix we used $$rAF to do the tick detection, however, with this
patch we intelligently use $$postDigest to do that for us and then
only issue a call to `$$rAF` if necessary.
2015-09-22 13:47:04 -07:00
Peter Bacon Darwin 7295c60ffb fix(ngMessages): prevent race condition with ngAnimate
If `ngMessage` tried to add a message back in that was about to be removed
after an animation, the NgMessageController got confused and tried to detach
the newly added message, when the pending node was destroyed.

This change applies a unique `attachId` to the message object and its DOM
node when it is attached. This is then checked when a DOM node is being
destroyed to prevent unwanted calls to `detach`.

Closes #12856
Closes #12903
2015-09-22 20:53:40 +01:00
Martin Staffa fa01571036 docs(guide/Directives): fix link formatting
Closes #12909;
2015-09-22 13:12:52 +02:00
Martin Staffa dbc698517f fix(ngOptions): prevent frozen select ui in IE
In certain scenarios, IE10/11/Edge create unresponsive select elements.
The following contribute to the bug:
- There need to be at least 2 selects next to each other
- The option elements are added via javascript
- the option.value is accessed before it is set
- the option.label is added after the option.value has been set
- The first select is wrappend in an element with display: inline or
display: inline-block,

This cannot be tested in a unit-test or e2e test.

Closes #11314
Closes #11795
2015-09-22 13:05:06 +02:00
Lucas Galfaso a7f3761eda fix($parse): block assigning to fields of a constructor
Throw when assigning to a field of a constructor.

Closes #12860
2015-09-22 10:44:27 +01:00
Jason Bedard 5a98e806ef fix($compile): use createMap() for $$observe listeners when initialized from attr interpolation
Closes #10446
2015-09-21 19:05:20 +01:00
Ivan Verevkin 808f984ec0 docs($cacheFactory): fix call to isUndefined() in example
Closes #12899
2015-09-21 15:51:49 +03:00
Lucas Mirelmann 698af191de fix($parse): do not convert to string computed properties multiple times
Do not convert to string properties multiple times.
2015-09-19 22:21:59 +02:00
Sjur Bakka 7a413df5e4 feat($http): add $xhrFactory service to enable creation of custom xhr objects
Closes #2318
Closes #9319
Closes #12159
2015-09-18 19:52:50 +01:00
Peter Bacon Darwin 4994acd26e fix(filters): ensure formatNumber observes i18n decimal separators
Closes #10342
Closes #12850
2015-09-18 13:45:29 +01:00
43 changed files with 943 additions and 244 deletions
+2 -1
View File
@@ -48,7 +48,7 @@ install:
- npm config set loglevel http
- npm install -g npm@2.5
# Instal npm dependecies and ensure that npm cache is not stale
- scripts/npm/install-dependencies.sh
- npm install
before_script:
- mkdir -p $LOGS_DIR
@@ -61,6 +61,7 @@ script:
- ./scripts/travis/build.sh
after_script:
- ./scripts/travis/tear_down_browser_provider.sh
- ./scripts/travis/print_logs.sh
notifications:
+52
View File
@@ -1,3 +1,55 @@
<a name="1.4.7"></a>
# 1.4.7 dark-luminescence (2015-09-29)
## Bug Fixes
- **$compile:** use createMap() for $$observe listeners when initialized from attr interpolation
([5a98e806](https://github.com/angular/angular.js/commit/5a98e806ef3c59916bb4668268125610b11effe8),
[#10446](https://github.com/angular/angular.js/issues/10446))
- **$parse:**
- block assigning to fields of a constructor
([a7f3761e](https://github.com/angular/angular.js/commit/a7f3761eda5309f76b73c6fb1d3173a270112899),
[#12860](https://github.com/angular/angular.js/issues/12860))
- do not convert to string computed properties multiple times
([698af191](https://github.com/angular/angular.js/commit/698af191ded2465ca4e0f97959b75fede5a531ab))
- **filters:** ensure `formatNumber` observes i18n decimal separators
([4994acd2](https://github.com/angular/angular.js/commit/4994acd26e582eec8a92b139bfc09ca79a9b8835),
[#10342](https://github.com/angular/angular.js/issues/10342), [#12850](https://github.com/angular/angular.js/issues/12850))
- **jqLite:** properly handle dash-delimited node names in `jqLiteBuildFragment`
([cdd1227a](https://github.com/angular/angular.js/commit/cdd1227a308edd34d31b67f338083b6e0c4c0db9),
[#10617](https://github.com/angular/angular.js/issues/10617), [#12759](https://github.com/angular/angular.js/issues/12759))
- **ngAnimate:**
- ensure anchoring uses body as a container when needed
([9d3704ca](https://github.com/angular/angular.js/commit/9d3704ca467081f16b71b011eb50c53d5cdb2f34),
[#12872](https://github.com/angular/angular.js/issues/12872))
- callback detection should only use RAF when necessary
([fa8c399f](https://github.com/angular/angular.js/commit/fa8c399fadc30b78710868fe59d2930fdc17c7a5))
- **ngMessages:** prevent race condition with ngAnimate
([7295c60f](https://github.com/angular/angular.js/commit/7295c60ffb9f2e4f32043c538ace740b187f565a),
[#12856](https://github.com/angular/angular.js/issues/12856), [#12903](https://github.com/angular/angular.js/issues/12903))
- **ngOptions:**
- skip comments when looking for option elements
([68d4dc5b](https://github.com/angular/angular.js/commit/68d4dc5b71b23e4c7c2650e6da3d7200de99f1ae),
[#12190](https://github.com/angular/angular.js/issues/12190))
- prevent frozen select ui in IE
([dbc69851](https://github.com/angular/angular.js/commit/dbc698517ff620b3a6279f65d4a9b6e3c15087b9),
[#11314](https://github.com/angular/angular.js/issues/11314), [#11795](https://github.com/angular/angular.js/issues/11795))
## Features
- **$animateCss:** add support for temporary styles via `cleanupStyles`
([e52d731b](https://github.com/angular/angular.js/commit/e52d731bfd1fbb6c616125fbde2fb365722254b7),
[#12930](https://github.com/angular/angular.js/issues/12930))
- **$http:** add `$xhrFactory` service to enable creation of custom xhr objects
([7a413df5](https://github.com/angular/angular.js/commit/7a413df5e47e04e20a1c93d35922050bbcbfb492),
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
## Breaking Changes
<a name="1.4.6"></a>
# 1.4.6 multiplicative-elevation (2015-09-17)
+1 -1
View File
@@ -305,7 +305,7 @@ module.exports = function(grunt) {
shell: {
"npm-install": {
command: path.normalize('scripts/npm/install-dependencies.sh')
command: 'node scripts/npm/check-node-modules.js'
},
"promises-aplus-tests": {
-1
View File
@@ -92,7 +92,6 @@ var angularFiles = {
'angularModules': {
'ngAnimate': [
'src/ngAnimate/shared.js',
'src/ngAnimate/body.js',
'src/ngAnimate/rafScheduler.js',
'src/ngAnimate/animateChildrenDirective.js',
'src/ngAnimate/animateCss.js',
+3 -4
View File
@@ -43,8 +43,7 @@ mirrors the process of compiling source code in
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
determines when to use a given directive.
Similar to the terminology used when an [element **matches** a selector]
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
directive when the directive is part of its declaration.
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
@@ -903,7 +902,7 @@ to which tab is active.
restrict: 'E',
transclude: true,
scope: {},
controller: function($scope) {
controller: ['$scope', function($scope) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
@@ -919,7 +918,7 @@ to which tab is active.
}
panes.push(pane);
};
},
}],
templateUrl: 'my-tabs.html'
};
})
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -e -o pipefail
echo "Shutting down Browserstack tunnel"
echo "TODO: implement me"
exit 1
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -e -o pipefail
echo "Shutting down Sauce Connect tunnel"
killall sc
while [[ -n `ps -ef | grep "sauce-connect-" | grep -v "grep"` ]]; do
printf "."
sleep .5
done
echo ""
echo "Sauce Connect tunnel has been shut down"
+4
View File
@@ -13,6 +13,10 @@
"npm": "~2.5"
},
"engineStrict": true,
"scripts": {
"preinstall": "node scripts/npm/check-node-modules.js --purge",
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js"
},
"devDependencies": {
"angular-benchpress": "0.x.x",
"benchmark": "1.x.x",
+74
View File
@@ -0,0 +1,74 @@
// Implementation based on:
// https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js
'use strict';
// Imports
var fs = require('fs');
var path = require('path');
// Constants
var PROJECT_ROOT = path.join(__dirname, '../../');
var NODE_MODULES_DIR = 'node_modules';
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
// Run
_main();
// Functions - Definitions
function _main() {
var purgeIfStale = process.argv.indexOf('--purge') !== -1;
process.chdir(PROJECT_ROOT);
checkNodeModules(purgeIfStale);
}
function checkNodeModules(purgeIfStale) {
var nodeModulesOk = compareMarkerFiles(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE);
if (nodeModulesOk) {
console.log(':-) npm dependencies are looking good!');
} else if (purgeIfStale) {
console.log(':-( npm dependencies are stale or in an unknown state!');
console.log(' Purging \'' + NODE_MODULES_DIR + '\'...');
deleteDirSync(NODE_MODULES_DIR);
} else {
var separator = new Array(81).join('!');
console.warn(separator);
console.warn(':-( npm dependencies are stale or in an unknown state!');
console.warn('You can rebuild the dependencies by running `npm install`.');
console.warn(separator);
}
return nodeModulesOk;
}
function compareMarkerFiles(markerFilePath, cachedMarkerFilePath) {
if (!fs.existsSync(cachedMarkerFilePath)) return false;
var opts = {encoding: 'utf-8'};
var markerContent = fs.readFileSync(markerFilePath, opts);
var cachedMarkerContent = fs.readFileSync(cachedMarkerFilePath, opts);
return markerContent === cachedMarkerContent;
}
// Custom implementation of `rm -rf` that works consistently across OSes
function deleteDirSync(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(deleteDirOrFileSync);
fs.rmdirSync(path);
}
// Helpers
function deleteDirOrFileSync(subpath) {
var curPath = path + '/' + subpath;
if (fs.lstatSync(curPath).isDirectory()) {
deleteDirSync(curPath);
} else {
fs.unlinkSync(curPath);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
'use strict';
// Imports
var fs = require('fs');
var path = require('path');
// Constants
var PROJECT_ROOT = path.join(__dirname, '../../');
var NODE_MODULES_DIR = 'node_modules';
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
// Run
_main();
// Functions - Definitions
function _main() {
process.chdir(PROJECT_ROOT);
copyFile(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE, onCopied);
}
// Implementation based on:
// https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js#answer-21995878
function copyFile(srcPath, dstPath, callback) {
var callbackCalled = false;
if (!fs.existsSync(srcPath)) {
done(new Error('Missing source file: ' + srcPath));
return;
}
var rs = fs.createReadStream(srcPath);
rs.on('error', done);
var ws = fs.createWriteStream(dstPath);
ws.on('error', done);
ws.on('finish', done);
rs.pipe(ws);
// Helpers
function done(err) {
if (callback && !callbackCalled) {
callbackCalled = true;
callback(err);
}
}
}
function onCopied(err) {
if (err) {
var separator = new Array(81).join('!');
console.error(separator);
console.error(
'Failed to copy `' + NPM_SHRINKWRAP_FILE + '` to `' + NPM_SHRINKWRAP_CACHED_FILE + '`:');
console.error(err);
console.error(separator);
}
}
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
set -e
SHRINKWRAP_FILE=npm-shrinkwrap.json
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
echo 'No shrinkwrap changes detected. npm install will be skipped...';
else
echo 'Blowing away node_modules and reinstalling npm dependencies...'
rm -rf node_modules
npm install
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
echo 'npm install successful!'
fi
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Has to be run from project root directory.
./lib/${BROWSER_PROVIDER}/teardown_tunnel.sh
+2
View File
@@ -72,6 +72,7 @@
$HttpParamSerializerProvider,
$HttpParamSerializerJQLikeProvider,
$HttpBackendProvider,
$xhrFactoryProvider,
$LocationProvider,
$LogProvider,
$ParseProvider,
@@ -230,6 +231,7 @@ function publishExternalAPI(angular) {
$httpParamSerializer: $HttpParamSerializerProvider,
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
$httpBackend: $HttpBackendProvider,
$xhrFactory: $xhrFactoryProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
+7
View File
@@ -43,6 +43,13 @@ var $CoreAnimateCssProvider = function() {
};
return function(element, options) {
// there is no point in applying the styles since
// there is no animation that goes on at all in
// this version of $animateCss.
if (options.cleanupStyles) {
options.from = options.to = null;
}
if (options.from) {
element.css(options.from);
options.from = null;
+2 -2
View File
@@ -67,10 +67,10 @@
$scope.keys = [];
$scope.cache = $cacheFactory('cacheId');
$scope.put = function(key, value) {
if (isUndefined($scope.cache.get(key))) {
if (angular.isUndefined($scope.cache.get(key))) {
$scope.keys.push(key);
}
$scope.cache.put(key, isUndefined(value) ? null : value);
$scope.cache.put(key, angular.isUndefined(value) ? null : value);
};
}]);
</file>
+1 -1
View File
@@ -2427,7 +2427,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
compile: function() {
return {
pre: function attrInterpolatePreLinkFn(scope, element, attr) {
var $$observers = (attr.$$observers || (attr.$$observers = {}));
var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
throw $compileMinErr('nodomevents',
+1 -1
View File
@@ -66,7 +66,7 @@
* </file>
* </example>
*
* ### Example - splitting on whitespace
* ### Example - splitting on newline
* <example name="ngList-directive-newlines">
* <file name="index.html">
* <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
+3 -1
View File
@@ -22,7 +22,9 @@ var ngModelMinErr = minErr('ngModel');
* @ngdoc type
* @name ngModel.NgModelController
*
* @property {string} $viewValue Actual string value in the view.
* @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
* String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
* is set.
* @property {*} $modelValue The value in the model that the control is bound to.
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
the control reads value from the DOM. The functions are called in array order, each passing
+10 -2
View File
@@ -579,11 +579,16 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
function updateOptionElement(option, element) {
option.element = element;
element.disabled = option.disabled;
if (option.value !== element.value) element.value = option.selectValue;
// NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
// selects in certain circumstances when multiple selects are next to each other and display
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
// See https://github.com/angular/angular.js/issues/11314 for more info.
// This is unfortunately untestable with unit / e2e tests
if (option.label !== element.label) {
element.label = option.label;
element.textContent = option.label;
}
if (option.value !== element.value) element.value = option.selectValue;
}
function addOrReuseElement(parent, current, type, templateElement) {
@@ -624,7 +629,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
if (emptyOption_ || unknownOption_) {
while (current &&
(current === emptyOption_ ||
current === unknownOption_)) {
current === unknownOption_ ||
emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) {
// Empty options might have directives that transclude
// and insert comments (e.g. ngIf)
current = current.nextSibling;
}
}
+1
View File
@@ -214,6 +214,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (fractionSize > 0 && number < 1) {
formatedText = number.toFixed(fractionSize);
number = parseFloat(formatedText);
formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
}
}
+23 -31
View File
@@ -425,28 +425,18 @@ function $HttpProvider() {
*
*
* ## General usage
* The `$http` service is a function which takes a single argument — a configuration object —
* The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object}
* that is used to generate an HTTP request and returns a {@link ng.$q promise}.
*
* ```js
* // Simple GET request example :
* $http.get('/someUrl').
* then(function(response) {
* // Simple GET request example:
* $http({
* method: 'GET',
* url: '/someUrl'
* }).then(function successCallback(response) {
* // this callback will be called asynchronously
* // when the response is available
* }, function(response) {
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* });
* ```
*
* ```js
* // Simple POST request example (passing data) :
* $http.post('/someUrl', {msg:'hello word!'}).
* then(function(response) {
* // this callback will be called asynchronously
* // when the response is available
* }, function(response) {
* }, function errorCallback(response) {
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* });
@@ -466,25 +456,16 @@ function $HttpProvider() {
* XMLHttpRequest will transparently follow it, meaning that the error callback will not be
* called for such responses.
*
* ## Writing Unit Tests that use $http
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
* request using trained responses.
*
* ```
* $httpBackend.expectGET(...);
* $http.get(...);
* $httpBackend.flush();
* ```
*
* ## Shortcut methods
*
* Shortcut methods are also available. All shortcut methods require passing in the URL, and
* request data must be passed in for POST/PUT requests.
* request data must be passed in for POST/PUT requests. An optional config can be passed as the
* last argument.
*
* ```js
* $http.get('/someUrl').then(successCallback);
* $http.post('/someUrl', data).then(successCallback);
* $http.get('/someUrl', config).then(successCallback, errorCallback);
* $http.post('/someUrl', data, config).then(successCallback, errorCallback);
* ```
*
* Complete list of shortcut methods:
@@ -498,6 +479,17 @@ function $HttpProvider() {
* - {@link ng.$http#patch $http.patch}
*
*
* ## Writing Unit Tests that use $http
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
* request using trained responses.
*
* ```
* $httpBackend.expectGET(...);
* $http.get(...);
* $httpBackend.flush();
* ```
*
* ## Deprecation Notice
* <div class="alert alert-danger">
* The `$http` legacy promise methods `success` and `error` have been deprecated.
@@ -655,7 +647,7 @@ function $HttpProvider() {
*
* There are two kinds of interceptors (and two kinds of rejection interceptors):
*
* * `request`: interceptors get called with a http `config` object. The function is free to
* * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
* modify the `config` object or create a new one. The function needs to return the `config`
* object directly, or a promise containing the `config` or a new `config` object.
* * `requestError`: interceptor gets called when a previous interceptor threw an error or
+31 -5
View File
@@ -1,7 +1,32 @@
'use strict';
function createXhr() {
return new window.XMLHttpRequest();
/**
* @ngdoc service
* @name $xhrFactory
*
* @description
* Factory function used to create XMLHttpRequest objects.
*
* Replace or decorate this service to create your own custom XMLHttpRequest objects.
*
* ```
* angular.module('myApp', [])
* .factory('$xhrFactory', function() {
* return function createXhr(method, url) {
* return new window.XMLHttpRequest({mozSystem: true});
* };
* });
* ```
*
* @param {string} method HTTP method of the request (GET, POST, PUT, ..)
* @param {string} url URL of the request.
*/
function $xhrFactoryProvider() {
this.$get = function() {
return function createXhr() {
return new window.XMLHttpRequest();
};
};
}
/**
@@ -9,6 +34,7 @@ function createXhr() {
* @name $httpBackend
* @requires $window
* @requires $document
* @requires $xhrFactory
*
* @description
* HTTP backend used by the {@link ng.$http service} that delegates to
@@ -21,8 +47,8 @@ function createXhr() {
* $httpBackend} which can be trained with responses.
*/
function $HttpBackendProvider() {
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]);
this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
}];
}
@@ -46,7 +72,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
});
} else {
var xhr = createXhr();
var xhr = createXhr(method, url);
xhr.open(method, url, true);
forEach(headers, function(value, key) {
+48 -8
View File
@@ -38,20 +38,30 @@ var $parseMinErr = minErr('$parse');
function ensureSafeMemberName(name, fullExpression) {
if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__") {
throw $parseMinErr('isecfld',
'Attempting to access a disallowed field in Angular expressions! '
+ 'Expression: {0}', fullExpression);
}
return name;
}
function getStringValue(name, fullExpression) {
// From the JavaScript docs:
// Property names must be strings. This means that non-string objects cannot be used
// as keys in an object. Any non-string object, including a number, is typecasted
// into a string via the toString method.
//
// So, to ensure that we are checking the same `name` that JavaScript would use,
// we cast it to a string, if possible
name = (isObject(name) && name.toString) ? name.toString() : name;
if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__") {
throw $parseMinErr('isecfld',
'Attempting to access a disallowed field in Angular expressions! '
// we cast it to a string, if possible.
// Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
// this is, this will handle objects that misbehave.
name = name + '';
if (!isString(name)) {
throw $parseMinErr('iseccst',
'Cannot convert object to primitive value! '
+ 'Expression: {0}', fullExpression);
}
return name;
@@ -102,6 +112,16 @@ function ensureSafeFunction(obj, fullExpression) {
}
}
function ensureSafeAssignContext(obj, fullExpression) {
if (obj) {
if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
throw $parseMinErr('isecaf',
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
}
}
}
var OPERATORS = createMap();
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
@@ -816,6 +836,8 @@ ASTCompiler.prototype = {
'ensureSafeMemberName',
'ensureSafeObject',
'ensureSafeFunction',
'getStringValue',
'ensureSafeAssignContext',
'ifDefined',
'plus',
'text',
@@ -824,6 +846,8 @@ ASTCompiler.prototype = {
ensureSafeMemberName,
ensureSafeObject,
ensureSafeFunction,
getStringValue,
ensureSafeAssignContext,
ifDefined,
plusFn,
expression);
@@ -967,6 +991,7 @@ ASTCompiler.prototype = {
if (ast.computed) {
right = self.nextId();
self.recurse(ast.property, right);
self.getStringValue(right);
self.addEnsureSafeMemberName(right);
if (create && create !== 1) {
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
@@ -1050,6 +1075,7 @@ ASTCompiler.prototype = {
self.if_(self.notNull(left.context), function() {
self.recurse(ast.right, right);
self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
self.addEnsureSafeAssignContext(left.context);
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
self.assign(intoId, expression);
recursionFn(intoId || expression);
@@ -1175,6 +1201,10 @@ ASTCompiler.prototype = {
this.current().body.push(this.ensureSafeFunction(item), ';');
},
addEnsureSafeAssignContext: function(item) {
this.current().body.push(this.ensureSafeAssignContext(item), ';');
},
ensureSafeObject: function(item) {
return 'ensureSafeObject(' + item + ',text)';
},
@@ -1187,6 +1217,14 @@ ASTCompiler.prototype = {
return 'ensureSafeFunction(' + item + ',text)';
},
getStringValue: function(item) {
this.assign(item, 'getStringValue(' + item + ',text)');
},
ensureSafeAssignContext: function(item) {
return 'ensureSafeAssignContext(' + item + ',text)';
},
lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
var self = this;
return function() {
@@ -1364,6 +1402,7 @@ ASTInterpreter.prototype = {
var lhs = left(scope, locals, assign, inputs);
var rhs = right(scope, locals, assign, inputs);
ensureSafeObject(lhs.value, self.expression);
ensureSafeAssignContext(lhs.context);
lhs.context[lhs.name] = rhs;
return context ? {value: rhs} : rhs;
};
@@ -1561,6 +1600,7 @@ ASTInterpreter.prototype = {
var value;
if (lhs != null) {
rhs = right(scope, locals, assign, inputs);
rhs = getStringValue(rhs);
ensureSafeMemberName(rhs, expression);
if (create && create !== 1 && lhs && !(lhs[rhs])) {
lhs[rhs] = {};
+42 -3
View File
@@ -187,7 +187,7 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
* to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
* * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
* * `transition` - The raw CSS transition style that will be used (e.g. `1s linear all`).
* * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
* * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
* * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
* * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
@@ -204,6 +204,10 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
* * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
* * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
* the animation is closed. This is useful for when the styles are used purely for the sake of
* the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
* By default this value is set to `false`.
*
* @return {object} an object with start and end methods and details about the animation.
*
@@ -324,6 +328,23 @@ function createLocalCacheLookup() {
};
}
// we do not reassign an already present style value since
// if we detect the style property value again we may be
// detecting styles that were added via the `from` styles.
// We make use of `isDefined` here since an empty string
// or null value (which is what getPropertyValue will return
// for a non-existing style) will still be marked as a valid
// value for the style (a falsy value implies that the style
// is to be removed at the end of the animation). If we had a simple
// "OR" statement then it would not be enough to catch that.
function registerRestorableStyles(backup, node, properties) {
forEach(properties, function(prop) {
backup[prop] = isDefined(backup[prop])
? backup[prop]
: node.style.getPropertyValue(prop);
});
}
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();
@@ -424,6 +445,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
return function init(element, options) {
var restoreStyles = {};
var node = getDomNode(element);
if (!node
|| !node.parentNode
@@ -625,7 +647,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
stagger.animationDuration === 0;
}
applyAnimationFromStyles(element, options);
if (options.from) {
if (options.cleanupStyles) {
registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
}
applyAnimationFromStyles(element, options);
}
if (flags.blockTransition || flags.blockKeyframeAnimation) {
applyBlocking(maxDuration);
@@ -692,6 +719,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
applyAnimationClasses(element, options);
applyAnimationStyles(element, options);
if (Object.keys(restoreStyles).length) {
forEach(restoreStyles, function(value, prop) {
value ? node.style.setProperty(prop, value)
: node.style.removeProperty(prop);
});
}
// the reason why we have this option is to allow a synchronous closing callback
// that is fired as SOON as the animation ends (when the CSS is removed) or if
// the animation never takes off at all. A good example is a leave animation since
@@ -886,7 +920,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
element.on(events.join(' '), onAnimationProgress);
applyAnimationToStyles(element, options);
if (options.to) {
if (options.cleanupStyles) {
registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
}
applyAnimationToStyles(element, options);
}
}
function onAnimationExpired() {
+13 -4
View File
@@ -9,16 +9,25 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$$body', '$sniffer', '$$jqLite',
function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $$body, $sniffer, $$jqLite) {
function isDocumentFragment(node) {
return node.parentNode && node.parentNode.nodeType === 11;
}
this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
// only browsers that support these properties can render animations
if (!$sniffer.animations && !$sniffer.transitions) return noop;
var bodyNode = getDomNode($$body);
var bodyNode = $document[0].body;
var rootNode = getDomNode($rootElement);
var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
var rootBodyElement = jqLite(
// this is to avoid using something that exists outside of the body
// we also special case the doc fragement case because our unit test code
// appends the $rootElement to the body after the app has been bootstrapped
isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
);
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+40 -13
View File
@@ -66,15 +66,33 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
});
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$body', '$$HashMap',
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
function($$rAF, $rootScope, $rootElement, $document, $$body, $$HashMap,
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
var activeAnimationsLookup = new $$HashMap();
var disabledElementsLookup = new $$HashMap();
var animationsEnabled = null;
function postDigestTaskFactory() {
var postDigestCalled = false;
return function(fn) {
// we only issue a call to postDigest before
// it has first passed. This prevents any callbacks
// from not firing once the animation has completed
// since it will be out of the digest cycle.
if (postDigestCalled) {
fn();
} else {
$rootScope.$$postDigest(function() {
postDigestCalled = true;
fn();
});
}
};
}
// Wait until all directive and route-related templates are downloaded and
// compiled. The $templateRequest.totalPendingRequests variable keeps track of
// all of the remote templates being currently downloaded. If there are no
@@ -137,14 +155,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return matches;
}
function triggerCallback(event, element, phase, data) {
$$rAF(function() {
forEach(findCallbacks(element, event), function(callback) {
callback(element, phase, data);
});
});
}
return {
on: function(event, container, callback) {
var node = extractElementNode(container);
@@ -239,6 +249,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// These methods will become available after the digest has passed
var runner = new $$AnimateRunner();
// this is used to trigger callbacks in postDigest mode
var runInNextPostDigestOrNow = postDigestTaskFactory();
if (isArray(options.addClass)) {
options.addClass = options.addClass.join(' ');
}
@@ -459,7 +472,20 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return runner;
function notifyProgress(runner, event, phase, data) {
triggerCallback(event, element, phase, data);
runInNextPostDigestOrNow(function() {
var callbacks = findCallbacks(element, event);
if (callbacks.length) {
// do not optimize this call here to RAF because
// we don't know how heavy the callback code here will
// be and if this code is buffered then this can
// lead to a performance regression.
$$rAF(function() {
forEach(callbacks, function(callback) {
callback(element, phase, data);
});
});
}
});
runner.progress(event, phase, data);
}
@@ -502,7 +528,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
function areAnimationsAllowed(element, parentElement, event) {
var bodyElementDetected = isMatchingElement(element, $$body) || element[0].nodeName === 'HTML';
var bodyElement = jqLite($document[0].body);
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
var animateChildren;
@@ -558,7 +585,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
if (!bodyElementDetected) {
// we also need to ensure that the element is or will be apart of the body element
// otherwise it is pointless to even issue an animation to be rendered
bodyElementDetected = isMatchingElement(parentElement, $$body);
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
}
parentElement = parentElement.parent();
-7
View File
@@ -1,7 +0,0 @@
'use strict';
function $$BodyProvider() {
this.$get = ['$document', function($document) {
return jqLite($document[0].body);
}];
}
-3
View File
@@ -2,7 +2,6 @@
/* global angularAnimateModule: true,
$$BodyProvider,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
@@ -742,8 +741,6 @@
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
*/
angular.module('ngAnimate', [])
.provider('$$body', $$BodyProvider)
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
+8 -1
View File
@@ -325,6 +325,9 @@ angular.module('ngMessages', [])
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;
this.getAttachId = function getAttachId() { return nextAttachId++; };
var messages = this.messages = {};
var renderLater, cachedCollection;
@@ -636,11 +639,15 @@ function ngMessageDirectiveFactory(restrict) {
$animate.enter(elm, null, element);
currentElement = elm;
// Each time we attach this node to a message we get a new id that we can match
// when we are destroying the node later.
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
// in the event that the parent element is destroyed
// by any other structural directive then it's time
// to deregister the message from the controller
currentElement.on('$destroy', function() {
if (currentElement) {
if (currentElement && currentElement.$$attachId === $$attachId) {
ngMessagesCtrl.deregister(commentNode);
messageCtrl.detach();
}
+32
View File
@@ -115,6 +115,38 @@ describe("$animateCss", function() {
expect(cancelSpy).toHaveBeenCalled();
expect(doneSpy).not.toHaveBeenCalled();
}));
it("should not bother applying the provided [from] and [to] styles to the element if [cleanupStyles] is present",
inject(function($animateCss, $rootScope) {
var animator = $animateCss(element, {
cleanupStyles: true,
from: { width: '100px' },
to: { width: '900px', height: '1000px' }
});
assertStyleIsEmpty(element, 'width');
assertStyleIsEmpty(element, 'height');
var runner = animator.start();
assertStyleIsEmpty(element, 'width');
assertStyleIsEmpty(element, 'height');
triggerRAF();
assertStyleIsEmpty(element, 'width');
assertStyleIsEmpty(element, 'height');
runner.end();
assertStyleIsEmpty(element, 'width');
assertStyleIsEmpty(element, 'height');
function assertStyleIsEmpty(element, prop) {
expect(element[0].style.getPropertyValue(prop)).toBeFalsy();
}
}));
});
});
+16
View File
@@ -3577,6 +3577,22 @@ describe('$compile', function() {
});
});
it('should be able to interpolate attribute names which are present in Object.prototype', function() {
var attrs;
module(function() {
directive('attrExposer', valueFn({
link: function($scope, $element, $attrs) {
attrs = $attrs;
}
}));
});
inject(function($compile, $rootScope) {
$compile('<div attr-exposer to-string="{{1 + 1}}">')($rootScope);
$rootScope.$apply();
expect(attrs.toString).toBe('2');
});
});
it('should not initialize scope value if optional expression binding is not passed', inject(function($compile) {
compile('<div my-component></div>');
+2 -2
View File
@@ -468,12 +468,12 @@ describe('ngClass animations', function() {
};
});
});
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $$body) {
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $document) {
$animate.enabled(true);
$rootScope.val = 'crazy';
element = angular.element('<div ng-class="val"></div>');
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
+27 -1
View File
@@ -2029,7 +2029,9 @@ describe('ngOptions', function() {
expect(option.text()).toBe('is blank');
});
it('should support option without a value attribute', function() {
it('should be ignored when it has no value attribute', function() {
// The option value is set to the textContent if there's no value attribute,
// so in that case it doesn't count as a blank option
createSingleSelect('<option>--select--</option>');
scope.$apply(function() {
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
@@ -2084,6 +2086,30 @@ describe('ngOptions', function() {
expect(element[0].selectedIndex).toEqual(0);
expect(scope.selected).toEqual([]);
});
it('should be possible to use ngIf in the blank option', function() {
var option;
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
scope.$apply(function() {
scope.values = [{name: 'A'}];
scope.isBlank = true;
});
expect(element.find('option').length).toBe(2);
option = element.find('option').eq(0);
expect(option.val()).toBe('');
expect(option.text()).toBe('blank');
scope.$apply(function() {
scope.isBlank = false;
});
expect(element.find('option').length).toBe(1);
option = element.find('option').eq(0);
expect(option.text()).toBe('A');
});
});
+2
View File
@@ -62,6 +62,8 @@ describe('filters', function() {
it('should format according different separators', function() {
var num = formatNumber(1234567.1, pattern, '.', ',', 2);
expect(num).toBe('1.234.567,10');
num = formatNumber(1e-14, pattern, '.', ',', 14);
expect(num).toBe('0,00000000000001');
});
it('should format with or without fractionSize', function() {
+7
View File
@@ -233,6 +233,13 @@ describe('$httpBackend', function() {
expect(MockXhr.$$lastInstance.withCredentials).toBe(true);
});
it('should call $xhrFactory with method and url', function() {
var mockXhrFactory = jasmine.createSpy('mockXhrFactory').andCallFake(createMockXhr);
$backend = createHttpBackend($browser, mockXhrFactory, $browser.defer, callbacks, fakeDocument);
$backend('GET', '/some-url', 'some-data', noop);
expect(mockXhrFactory).toHaveBeenCalledWith('GET', '/some-url');
});
describe('responseType', function() {
+38
View File
@@ -2692,6 +2692,15 @@ describe('parser', function() {
});
});
it('should prevent the exploit', function() {
expect(function() {
scope.$eval('(1)[{0: "__proto__", 1: "__proto__", 2: "__proto__", 3: "safe", length: 4, toString: [].pop}].foo = 1');
}).toThrow();
if (!msie || msie > 10) {
expect((1)['__proto__'].foo).toBeUndefined();
}
});
it('should prevent the exploit', function() {
expect(function() {
scope.$eval('' +
@@ -2703,6 +2712,35 @@ describe('parser', function() {
'');
}).toThrow();
});
it('should prevent assigning in the context of a constructor', function() {
expect(function() {
scope.$eval("''.constructor.join");
}).not.toThrow();
expect(function() {
scope.$eval("''.constructor.join = ''.constructor.join");
}).toThrow();
expect(function() {
scope.$eval("''.constructor[0] = ''");
}).toThrow();
expect(function() {
scope.$eval("(0).constructor[0] = ''");
}).toThrow();
expect(function() {
scope.$eval("{}.constructor[0] = ''");
}).toThrow();
// foo.constructor is the object constructor.
expect(function() {
scope.$eval("foo.constructor[0] = ''", {foo: {}});
}).toThrow();
// foo.constructor is not a constructor.
expect(function() {
scope.$eval("foo.constructor[0] = ''", {foo: {constructor: ''}});
}).not.toThrow();
expect(function() {
scope.$eval("objConstructor = {}.constructor; objConstructor.join = ''");
}).toThrow();
});
});
it('should call the function from the received instance and not from a new one', function() {
+42 -3
View File
@@ -121,7 +121,7 @@ describe("ngAnimate $$animateCssDriver", function() {
var from, to, fromAnimation, toAnimation;
beforeEach(module(function() {
return function($rootElement, $$body) {
return function($rootElement, $document) {
from = element;
to = jqLite('<div></div>');
fromAnimation = { element: from, event: 'enter' };
@@ -129,8 +129,14 @@ describe("ngAnimate $$animateCssDriver", function() {
$rootElement.append(from);
$rootElement.append(to);
// we need to do this so that style detection works
$$body.append($rootElement);
var doc = $document[0];
// there is one test in here that expects the rootElement
// to superceed the body node
if (!$rootElement[0].contains(doc.body)) {
// we need to do this so that style detection works
jqLite(doc.body).append($rootElement);
}
};
}));
@@ -975,6 +981,39 @@ describe("ngAnimate $$animateCssDriver", function() {
expect(completed).toBe(true);
}));
it("should use <body> as the element container if the rootElement exists outside of the <body> tag", function() {
module(function($provide) {
$provide.factory('$rootElement', function($document) {
return jqLite($document[0].querySelector('html'));
});
});
inject(function($rootElement, $rootScope, $animate, $document) {
ss.addRule('.ending-element', 'width:9999px; height:6666px; display:inline-block;');
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
var toAnchor = jqLite('<div></div>');
to.append(toAnchor);
$rootElement.append(fromAnchor);
$rootElement.append(toAnchor);
var completed = false;
driver({
from: fromAnimation,
to: toAnimation,
anchors: [{
'out': fromAnchor,
'in': toAnchor
}]
}).start();
var clone = captureLog[2].element[0];
expect(clone.parentNode).toBe($document[0].body);
});
});
});
});
});
+149 -84
View File
@@ -35,12 +35,12 @@ describe("ngAnimate $animateCss", function() {
});
it("should return false if neither transitions or keyframes are supported by the browser",
inject(function($animateCss, $sniffer, $rootElement, $$body) {
inject(function($animateCss, $sniffer, $rootElement, $document) {
var animator;
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$sniffer.transitions = $sniffer.animations = false;
animator = $animateCss(element, {
@@ -54,13 +54,13 @@ describe("ngAnimate $animateCss", function() {
if (!browserSupportsCssAnimations()) return;
it("should not attempt an animation if animations are globally disabled",
inject(function($animateCss, $animate, $rootElement, $$body) {
inject(function($animateCss, $animate, $rootElement, $document) {
$animate.enabled(false);
var animator, element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
animator = $animateCss(element, {
duration: 10,
@@ -109,9 +109,9 @@ describe("ngAnimate $animateCss", function() {
describe("rAF usage", function() {
it("should buffer all requests into a single requestAnimationFrame call",
inject(function($animateCss, $$rAF, $rootScope, $$body, $rootElement) {
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var count = 0;
var runners = [];
@@ -149,8 +149,8 @@ describe("ngAnimate $animateCss", function() {
};
});
});
inject(function($animateCss, $$rAF, $$body, $rootElement) {
$$body.append($rootElement);
inject(function($animateCss, $$rAF, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
function makeRequest() {
var element = jqLite('<div></div>');
@@ -169,10 +169,10 @@ describe("ngAnimate $animateCss", function() {
describe("animator and runner", function() {
var animationDuration = 5;
var element, animator;
beforeEach(inject(function($animateCss, $rootElement, $$body) {
beforeEach(inject(function($animateCss, $rootElement, $document) {
element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
animator = $animateCss(element, {
event: 'enter',
@@ -365,10 +365,10 @@ describe("ngAnimate $animateCss", function() {
{ timeStamp: Date.now() + ((delay || 1) * 1000), elapsedTime: duration });
}
beforeEach(inject(function($rootElement, $$body) {
beforeEach(inject(function($rootElement, $document) {
element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
options = { event: 'enter', structural: true };
}));
@@ -638,9 +638,9 @@ describe("ngAnimate $animateCss", function() {
describe("staggering", function() {
it("should apply a stagger based when an active ng-EVENT-stagger class with a transition-delay is detected",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all');
@@ -679,9 +679,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should apply a stagger based when for all provided addClass/removeClass CSS classes",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.red-add-stagger,' +
'.blue-remove-stagger,' +
@@ -749,9 +749,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should block the transition animation between start and animate when staggered",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all;');
@@ -780,9 +780,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should block (pause) the keyframe animation between start and animate when staggered",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
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;');
@@ -809,9 +809,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should not apply a stagger if the transition delay value is inherited from a earlier CSS class",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
@@ -828,9 +828,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should apply a stagger only if the transition duration value is zero when inherited from a earlier CSS class",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
ss.addRule('.transition-animation.ng-enter-stagger',
@@ -854,9 +854,9 @@ describe("ngAnimate $animateCss", function() {
it("should ignore animation staggers if only transition animations were detected",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', prefix + 'animation-delay:0.2s');
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
@@ -874,9 +874,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should ignore transition staggers if only keyframe animations were detected",
inject(function($animateCss, $$body, $rootElement) {
inject(function($animateCss, $document, $rootElement) {
$$body.append($rootElement);
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;');
@@ -894,9 +894,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should start on the highest stagger value if both transition and keyframe staggers are used together",
inject(function($animateCss, $$body, $rootElement, $timeout, $browser) {
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.5s;' +
prefix + 'animation-delay:1s');
@@ -932,9 +932,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should apply the closing timeout ontop of the stagger timeout",
inject(function($animateCss, $$body, $rootElement, $timeout, $browser) {
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all;');
@@ -959,9 +959,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should apply the closing timeout ontop of the stagger timeout with an added delay",
inject(function($animateCss, $$body, $rootElement, $timeout, $browser) {
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');
@@ -986,9 +986,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should issue a stagger if a stagger value is provided in the options",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.ng-enter', 'transition:2s linear all');
var elm, i, elements = [];
@@ -1025,9 +1025,9 @@ describe("ngAnimate $animateCss", function() {
}));
it("should only add/remove classes once the stagger timeout has passed",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var element = jqLite('<div class="green"></div>');
$rootElement.append(element);
@@ -1052,13 +1052,13 @@ describe("ngAnimate $animateCss", function() {
describe("closing timeout", function() {
it("should close off the animation after 150% of the animation time has passed",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1075,13 +1075,13 @@ 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, $$body, $rootElement, $timeout) {
inject(function($animateCss, $document, $rootElement, $timeout) {
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1098,13 +1098,13 @@ describe("ngAnimate $animateCss", function() {
}));
it("should still resolve the animation once expired",
inject(function($animateCss, $$body, $rootElement, $timeout, $animate, $rootScope) {
inject(function($animateCss, $document, $rootElement, $timeout, $animate, $rootScope) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
@@ -1123,13 +1123,13 @@ describe("ngAnimate $animateCss", function() {
}));
it("should not resolve/reject after passing if the animation completed successfully",
inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope, $animate) {
inject(function($animateCss, $document, $rootElement, $timeout, $rootScope, $animate) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
@@ -1160,7 +1160,7 @@ describe("ngAnimate $animateCss", function() {
}));
it("should close all stacked animations after the last timeout runs on the same element",
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
inject(function($animateCss, $document, $rootElement, $timeout, $animate) {
var now = 0;
spyOn(Date, 'now').andCallFake(function() {
@@ -1177,7 +1177,7 @@ describe("ngAnimate $animateCss", function() {
var element = jqLite('<div class="elm"></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
// timeout will be at 1500s
animate(element, 'red', doneSpy);
@@ -1218,11 +1218,11 @@ describe("ngAnimate $animateCss", function() {
}));
it("should not throw an error any pending timeout requests resolve after the element has already been removed",
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
inject(function($animateCss, $document, $rootElement, $timeout, $animate) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.red', 'transition:1s linear all;');
@@ -1255,8 +1255,8 @@ describe("ngAnimate $animateCss", function() {
}
}));
return function($$body, $rootElement) {
$$body.append($rootElement);
return function($document, $rootElement) {
jqLite($document[0].body).append($rootElement);
};
}));
@@ -1340,7 +1340,7 @@ describe("ngAnimate $animateCss", function() {
});
it('should avoid applying the same cache to an element a follow-up animation is run on the same element',
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
function endTransition(element, elapsedTime) {
browserTrigger(element, 'transitionend',
@@ -1357,7 +1357,7 @@ describe("ngAnimate $animateCss", function() {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
startAnimation(element, 0.5, 'red');
expect(element.attr('style')).toContain('transition');
@@ -1377,14 +1377,14 @@ describe("ngAnimate $animateCss", function() {
}));
it("should clear cache if no animation so follow-up animation on the same element will not be from cache",
inject(function($animateCss, $rootElement, $$body, $$rAF) {
inject(function($animateCss, $rootElement, $document, $$rAF) {
var element = jqLite('<div class="rclass"></div>');
var options = {
event: 'enter',
structural: true
};
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, options);
expect(animator.$$willAnimate).toBeFalsy();
@@ -1396,11 +1396,11 @@ describe("ngAnimate $animateCss", function() {
}));
it('should apply a custom temporary class when a non-structural animation is used',
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$animateCss(element, {
event: 'super',
@@ -1416,10 +1416,10 @@ describe("ngAnimate $animateCss", function() {
describe("structural animations", function() {
they('should decorate the element with the ng-$prop CSS class',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$animateCss(element, {
event: event,
@@ -1433,10 +1433,10 @@ describe("ngAnimate $animateCss", function() {
they('should decorate the element with the ng-$prop-active CSS class',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, {
event: event,
@@ -1454,10 +1454,10 @@ describe("ngAnimate $animateCss", function() {
they('should remove the ng-$prop and ng-$prop-active CSS classes from the element once the animation is done',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var animator = $animateCss(element, {
event: event,
@@ -1511,10 +1511,10 @@ describe("ngAnimate $animateCss", function() {
they('should place a CSS transition block after the preparation function to block accidental style changes',
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1541,10 +1541,10 @@ describe("ngAnimate $animateCss", function() {
they('should not place a CSS transition block if options.skipBlocking is provided',
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $$body, $window) {
inject(function($animateCss, $rootElement, $document, $window) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1582,10 +1582,10 @@ describe("ngAnimate $animateCss", function() {
they('should place a CSS transition block after the preparation function even if a duration is provided',
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1616,11 +1616,11 @@ describe("ngAnimate $animateCss", function() {
});
it('should allow multiple events to be animated at the same time',
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$animateCss(element, {
event: ['enter', 'leave', 'move'],
@@ -1688,10 +1688,10 @@ describe("ngAnimate $animateCss", function() {
they('should remove the class-$prop-add and class-$prop-active CSS classes from the element once the animation is done',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var options = {};
options.event = event;
@@ -1713,7 +1713,7 @@ describe("ngAnimate $animateCss", function() {
they('should allow the class duration styles to be recalculated once started if the CSS classes being applied result new transition styles',
['add', 'remove'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
@@ -1728,7 +1728,7 @@ describe("ngAnimate $animateCss", function() {
}
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var options = {};
options[event + 'Class'] = 'natural-class';
@@ -1749,13 +1749,13 @@ describe("ngAnimate $animateCss", function() {
they('should force the class-based values to be applied early if no options.applyClassEarly is used as an option',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $$body) {
inject(function($animateCss, $rootElement, $document) {
ss.addRule('.blue.ng-' + event, 'transition:2s linear all;');
var element = jqLite('<div class="red"></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
var runner = $animateCss(element, {
addClass: 'blue',
@@ -1790,8 +1790,8 @@ describe("ngAnimate $animateCss", function() {
describe("options", function() {
var element;
beforeEach(module(function() {
return function($rootElement, $$body) {
$$body.append($rootElement);
return function($rootElement, $document) {
jqLite($document[0].body).append($rootElement);
element = jqLite('<div></div>');
$rootElement.append(element);
@@ -2741,10 +2741,10 @@ describe("ngAnimate $animateCss", function() {
describe("[easing]", function() {
var element;
beforeEach(inject(function($$body, $rootElement) {
beforeEach(inject(function($document, $rootElement) {
element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
}));
it("should apply easing to a transition animation if it exists", inject(function($animateCss) {
@@ -2786,6 +2786,71 @@ describe("ngAnimate $animateCss", function() {
}));
});
describe("[cleanupStyles]", function() {
it("should cleanup [from] and [to] styles that have been applied for the animation when true",
inject(function($animateCss) {
var runner = $animateCss(element, {
duration: 1,
from: { background: 'gold' },
to: { color: 'brown' },
cleanupStyles: true
}).start();
assertStyleIsPresent(element, 'background', true);
assertStyleIsPresent(element, 'color', false);
triggerAnimationStartFrame();
assertStyleIsPresent(element, 'background', true);
assertStyleIsPresent(element, 'color', true);
runner.end();
assertStyleIsPresent(element, 'background', false);
assertStyleIsPresent(element, 'color', false);
function assertStyleIsPresent(element, style, bool) {
expect(element[0].style[style])[bool ? 'toBeTruthy' : 'toBeFalsy']();
}
}));
it('should restore existing overidden styles already present on the element when true',
inject(function($animateCss) {
element.css('height', '100px');
element.css('width', '111px');
var runner = $animateCss(element, {
duration: 1,
from: { height: '200px', 'font-size':'66px' },
to: { height: '300px', 'font-size': '99px', width: '222px' },
cleanupStyles: true
}).start();
assertStyle(element, 'height', '200px');
assertStyle(element, 'font-size', '66px');
assertStyle(element, 'width', '111px');
triggerAnimationStartFrame();
assertStyle(element, 'height', '300px');
assertStyle(element, 'width', '222px');
assertStyle(element, 'font-size', '99px');
runner.end();
assertStyle(element, 'width', '111px');
assertStyle(element, 'height', '100px');
expect(element[0].style.getPropertyValue('font-size')).not.toBe('66px');
function assertStyle(element, prop, value) {
expect(element[0].style.getPropertyValue(prop)).toBe(value);
}
}));
});
it('should round up long elapsedTime values to close off a CSS3 animation',
inject(function($animateCss) {
@@ -2812,13 +2877,13 @@ describe("ngAnimate $animateCss", function() {
describe('SVG', function() {
it('should properly apply transitions on an SVG element',
inject(function($animateCss, $rootScope, $compile, $$body, $rootElement) {
inject(function($animateCss, $rootScope, $compile, $document, $rootElement) {
var element = $compile('<svg width="500" height="500">' +
'<circle cx="15" cy="5" r="100" fill="orange" />' +
'</svg>')($rootScope);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$rootElement.append(element);
$animateCss(element, {
+92 -36
View File
@@ -26,12 +26,12 @@ describe("animations", function() {
});
});
inject(function($animate, $rootScope, $$body) {
inject(function($animate, $rootScope, $document) {
$animate.enabled(true);
element = jqLite('<div></div>');
$animate.enter(element, $$body);
$animate.enter(element, jqLite($document[0].body));
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
@@ -116,7 +116,7 @@ describe("animations", function() {
return overriddenAnimationRunner || defaultFakeAnimationRunner;
});
return function($rootElement, $q, $animate, $$AnimateRunner, $$body) {
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
defaultFakeAnimationRunner = new $$AnimateRunner();
$animate.enabled(true);
@@ -126,7 +126,7 @@ describe("animations", function() {
$rootElement.append(parent);
$rootElement.append(parent2);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
};
}));
@@ -749,7 +749,7 @@ describe("animations", function() {
}));
it("should disable all child animations for atleast one turn when a structural animation is issued",
inject(function($animate, $rootScope, $compile, $$body, $rootElement, $$AnimateRunner) {
inject(function($animate, $rootScope, $compile, $document, $rootElement, $$AnimateRunner) {
element = $compile(
'<div><div class="if-animation" ng-if="items.length">' +
@@ -759,7 +759,7 @@ describe("animations", function() {
'</div></div>'
)($rootScope);
$$body.append($rootElement);
jqLite($document[0].body).append($rootElement);
$rootElement.append(element);
var runner = new $$AnimateRunner();
@@ -890,7 +890,6 @@ describe("animations", function() {
});
$rootScope.$digest();
$animate.flush();
expect(capturedAnimation).toBeTruthy();
expect(runner1done).toBeFalsy();
@@ -910,7 +909,6 @@ describe("animations", function() {
});
$rootScope.$digest();
$animate.flush();
expect(capturedAnimation).toBeTruthy();
expect(runner2done).toBeFalsy();
@@ -990,8 +988,6 @@ describe("animations", function() {
var doneHandler = jasmine.createSpy('addClass done');
runner.done(doneHandler);
$animate.flush();
expect(doneHandler).not.toHaveBeenCalled();
$animate.removeClass(element, 'active-class');
@@ -1011,8 +1007,6 @@ describe("animations", function() {
var doneHandler = jasmine.createSpy('addClass done');
runner.done(doneHandler);
$animate.flush();
expect(doneHandler).not.toHaveBeenCalled();
$animate.addClass(element, 'active-class');
@@ -1284,8 +1278,8 @@ describe("animations", function() {
return new $$AnimateRunner();
};
});
return function($rootElement, $$body, $animate) {
$$body.append($rootElement);
return function($rootElement, $document, $animate) {
jqLite($document[0].body).append($rootElement);
parent = jqLite('<div class="parent"></div>');
element = jqLite('<div class="element"></div>');
child = jqLite('<div class="child"></div>');
@@ -1389,14 +1383,14 @@ describe("animations", function() {
}));
it('should allow an element to pinned elsewhere and still be available in animations',
inject(function($animate, $compile, $$body, $rootElement, $rootScope) {
inject(function($animate, $compile, $document, $rootElement, $rootScope) {
var innerParent = jqLite('<div></div>');
$$body.append(innerParent);
jqLite($document[0].body).append(innerParent);
innerParent.append($rootElement);
var element = jqLite('<div></div>');
$$body.append(element);
jqLite($document[0].body).append(element);
$animate.addClass(element, 'red');
$rootScope.$digest();
@@ -1412,16 +1406,16 @@ describe("animations", function() {
}));
it('should adhere to the disabled state of the hosted parent when an element is pinned',
inject(function($animate, $compile, $$body, $rootElement, $rootScope) {
inject(function($animate, $compile, $document, $rootElement, $rootScope) {
var innerParent = jqLite('<div></div>');
$$body.append(innerParent);
jqLite($document[0].body).append(innerParent);
innerParent.append($rootElement);
var innerChild = jqLite('<div></div>');
$rootElement.append(innerChild);
var element = jqLite('<div></div>');
$$body.append(element);
jqLite($document[0].body).append(element);
$animate.pin(element, innerChild);
@@ -1456,17 +1450,17 @@ describe("animations", function() {
};
});
return function($$body, $rootElement, $animate) {
$$body.append($rootElement);
return function($document, $rootElement, $animate) {
jqLite($document[0].body).append($rootElement);
$animate.enabled(true);
};
}));
it('should trigger a callback for an enter animation',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
var callbackTriggered = false;
$animate.on('enter', $$body, function() {
$animate.on('enter', jqLite($document[0].body), function() {
callbackTriggered = true;
});
@@ -1480,12 +1474,12 @@ describe("animations", function() {
}));
it('should fire the callback with the signature of (element, phase, data)',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
var capturedElement;
var capturedPhase;
var capturedData;
$animate.on('enter', $$body,
$animate.on('enter', jqLite($document[0].body),
function(element, phase, data) {
capturedElement = element;
@@ -1519,7 +1513,6 @@ describe("animations", function() {
element = jqLite('<div></div>');
$animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush();
expect(callbackTriggered).toBe(false);
}));
@@ -1544,13 +1537,13 @@ describe("animations", function() {
}));
it('should remove all the event-based event listeners when $animate.off(event) is called',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
element = jqLite('<div></div>');
var count = 0;
$animate.on('enter', element, counter);
$animate.on('enter', $$body, counter);
$animate.on('enter', jqLite($document[0].body), counter);
function counter(element, phase) {
count++;
@@ -1572,13 +1565,13 @@ describe("animations", function() {
}));
it('should remove the container-based event listeners when $animate.off(event, container) is called',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
element = jqLite('<div></div>');
var count = 0;
$animate.on('enter', element, counter);
$animate.on('enter', $$body, counter);
$animate.on('enter', jqLite($document[0].body), counter);
function counter(element, phase) {
if (phase === 'start') {
@@ -1592,7 +1585,7 @@ describe("animations", function() {
expect(count).toBe(2);
$animate.off('enter', $$body);
$animate.off('enter', jqLite($document[0].body));
$animate.enter(element, $rootElement);
$rootScope.$digest();
@@ -1638,13 +1631,13 @@ describe("animations", function() {
}));
it('should fire a `start` callback when the animation starts with the matching element',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
element = jqLite('<div></div>');
var capturedState;
var capturedElement;
$animate.on('enter', $$body, function(element, phase) {
$animate.on('enter', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
@@ -1658,13 +1651,13 @@ describe("animations", function() {
}));
it('should fire a `close` callback when the animation ends with the matching element',
inject(function($animate, $rootScope, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $document) {
element = jqLite('<div></div>');
var capturedState;
var capturedElement;
$animate.on('enter', $$body, function(element, phase) {
$animate.on('enter', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
@@ -1707,6 +1700,69 @@ describe("animations", function() {
expect(count).toBe(1);
}));
it('should always detect registered callbacks after one postDigest has fired',
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
var spy = jasmine.createSpy();
registerCallback();
var runner = $animate.enter(element, $rootElement);
registerCallback();
$rootScope.$digest();
registerCallback();
expect(spy.callCount).toBe(0);
$animate.flush();
// this is not 3 since the 3rd callback
// was added after the first callback
// was fired
expect(spy.callCount).toBe(2);
spy.reset();
runner.end();
$animate.flush();
// now we expect all three callbacks
// to fire when the animation ends since
// the callback detection happens again
expect(spy.callCount).toBe(3);
function registerCallback() {
$animate.on('enter', element, spy);
}
}));
it('should use RAF if there are detected callbacks within the hierachy of the element being animated',
inject(function($animate, $rootScope, $rootElement, $$rAF) {
var runner;
element = jqLite('<div></div>');
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
runner.end();
assertRAFsUsed(false);
var spy = jasmine.createSpy();
$animate.on('leave', element, spy);
runner = $animate.leave(element, $rootElement);
$rootScope.$digest();
runner.end();
assertRAFsUsed(true);
function assertRAFsUsed(bool) {
expect($$rAF.queue.length)[bool ? 'toBeGreaterThan' : 'toBe'](0);
}
}));
it('leave: should remove the element even if another animation is called after',
inject(function($animate, $rootScope, $rootElement) {
+2 -2
View File
@@ -836,8 +836,8 @@ describe('$$animation', function() {
element = jqLite('<div></div>');
parent = jqLite('<div></div>');
return function($$AnimateRunner, $q, $rootElement, $$body) {
$$body.append($rootElement);
return function($$AnimateRunner, $rootElement, $document) {
jqLite($document[0].body).append($rootElement);
$rootElement.append(parent);
mockedDriverFn = function(element, method, options, domOperation) {
-9
View File
@@ -1,9 +0,0 @@
'use strict';
describe('$$body', function() {
beforeEach(module('ngAnimate'));
it("should inject $document", inject(function($$body, $document) {
expect($$body).toEqual(jqLite($document[0].body));
}));
});
+34 -2
View File
@@ -7,12 +7,12 @@ describe('ngAnimate integration tests', function() {
var element, html, ss;
beforeEach(module(function() {
return function($rootElement, $document, $$body, $window, $animate) {
return function($rootElement, $document, $window, $animate) {
$animate.enabled(true);
ss = createMockStyleSheet($document, $window);
var body = $$body;
var body = jqLite($document[0].body);
html = function(element) {
body.append($rootElement);
$rootElement.append(element);
@@ -319,6 +319,38 @@ describe('ngAnimate integration tests', function() {
}
});
});
it('should trigger callbacks at the start and end of an animation',
inject(function($rootScope, $rootElement, $animate, $compile) {
ss.addRule('.animate-me', 'transition:2s linear all;');
var parent = jqLite('<div><div ng-if="exp" class="animate-me"></div></div>');
element = parent.find('div');
html(parent);
$compile(parent)($rootScope);
$rootScope.$digest();
var spy = jasmine.createSpy();
$animate.on('enter', parent, spy);
$rootScope.exp = true;
$rootScope.$digest();
element = parent.find('div');
$animate.flush();
expect(spy.callCount).toBe(1);
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
$animate.flush();
expect(spy.callCount).toBe(2);
dealoc(element);
}));
});
describe('JS animations', function() {
+44
View File
@@ -372,6 +372,50 @@ describe('ngMessages', function() {
expect(trim(element.text())).toEqual("Enter something");
}));
// issue #12856
it('should only detach the message object that is associated with the message node being removed',
inject(function($rootScope, $compile, $animate) {
// We are going to spy on the `leave` method to give us control over
// when the element is actually removed
spyOn($animate, 'leave');
// Create a basic ng-messages set up
element = $compile('<div ng-messages="col">' +
' <div ng-message="primary">Enter something</div>' +
'</div>')($rootScope);
// Trigger the message to be displayed
$rootScope.col = { primary: true };
$rootScope.$digest();
expect(messageChildren(element).length).toEqual(1);
var oldMessageNode = messageChildren(element)[0];
// Remove the message
$rootScope.col = { primary: undefined };
$rootScope.$digest();
// Since we have spied on the `leave` method, the message node is still in the DOM
expect($animate.leave).toHaveBeenCalledOnce();
var nodeToRemove = $animate.leave.mostRecentCall.args[0][0];
expect(nodeToRemove).toBe(oldMessageNode);
$animate.leave.reset();
// Add the message back in
$rootScope.col = { primary: true };
$rootScope.$digest();
// Simulate the animation completing on the node
jqLite(nodeToRemove).remove();
// We should not get another call to `leave`
expect($animate.leave).not.toHaveBeenCalled();
// There should only be the new message node
expect(messageChildren(element).length).toEqual(1);
var newMessageNode = messageChildren(element)[0];
expect(newMessageNode).not.toBe(oldMessageNode);
}));
it('should render animations when the active/inactive classes are added/removed', function() {
module('ngAnimate');