Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6d04b3a3d | |||
| e31560cf6b | |||
| 3d38fff8b4 | |||
| bc492c0fc1 | |||
| 162144202c | |||
| 05596527ed | |||
| 5fea3471e8 | |||
| 131e4014b8 | |||
| 01c5be4681 | |||
| fd9a03e147 | |||
| 6c17d02bc4 | |||
| 6a6f71f238 | |||
| cf686285c2 | |||
| 7dfedb732d | |||
| 760f2fb731 | |||
| c9705b7556 | |||
| 53ec33f07f | |||
| 884ef0dbcd | |||
| 010413f90a | |||
| 4f57236614 | |||
| 50bf029625 | |||
| eff52ad877 | |||
| e9ee492d35 | |||
| e415e916e8 | |||
| 07084e1c8b | |||
| 186a591228 | |||
| a80049fd0a | |||
| d158dd131e | |||
| 1147f21999 | |||
| bddd46c8ec | |||
| 80e7a45584 | |||
| 498365f219 | |||
| 056c849352 | |||
| 7d6e5a2d01 | |||
| d1c4766d14 | |||
| 98473835a2 | |||
| 870232bd05 | |||
| c31df32ca0 | |||
| df2b88e230 | |||
| 83451d552e | |||
| af7203e0b8 |
@@ -1,3 +1,71 @@
|
||||
<a name="1.2.7"></a>
|
||||
# 1.2.7 emoji-clairvoyance (2014-01-03)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- ensue class-based animations are always skipped before structural post-digest tasks are run
|
||||
([bc492c0f](https://github.com/angular/angular.js/commit/bc492c0fc17257ddf2bc5964e205379aa766b3d8),
|
||||
[#5582](https://github.com/angular/angular.js/issues/5582))
|
||||
- remove trailing `s` from computed transition duration styles
|
||||
([50bf0296](https://github.com/angular/angular.js/commit/50bf029625d603fc652f0f413e709f43803743db))
|
||||
- **$http:**
|
||||
([3d38fff8](https://github.com/angular/angular.js/commit/3d38fff8b4ea2fd60fadef2028ea4dcddfccb1a4))
|
||||
- use ActiveX XHR when making PATCH requests on IE8
|
||||
([6c17d02b](https://github.com/angular/angular.js/commit/6c17d02bc4cc02f478775d62e1f9f77da9da82ad),
|
||||
[#2518](https://github.com/angular/angular.js/issues/2518), [#5043](https://github.com/angular/angular.js/issues/5043))
|
||||
- fix 'type mismatch' error on IE8 after each request
|
||||
([fd9a03e1](https://github.com/angular/angular.js/commit/fd9a03e147aac7e952c6dda1f381fd4662276ba2))
|
||||
- Ignore multiple calls to onreadystatechange with readyState=4
|
||||
([4f572366](https://github.com/angular/angular.js/commit/4f57236614415eea919221ea5f99c4d8689b3267),
|
||||
[#5426](https://github.com/angular/angular.js/issues/5426))
|
||||
- **$injector:** remove the `INSTANTIATING` flag properly when done
|
||||
([186a5912](https://github.com/angular/angular.js/commit/186a5912288acfff0ee59dae29af83c37c987921),
|
||||
[#4361](https://github.com/angular/angular.js/issues/4361), [#5577](https://github.com/angular/angular.js/issues/5577))
|
||||
- **$location:**
|
||||
- remove base href domain if the URL begins with '//'
|
||||
([760f2fb7](https://github.com/angular/angular.js/commit/760f2fb73178e56c37397b3c5876f7dac96f0455),
|
||||
[#5606](https://github.com/angular/angular.js/issues/5606))
|
||||
- fix $location.path() behaviour when $locationChangeStart is triggered by the browser
|
||||
([cf686285](https://github.com/angular/angular.js/commit/cf686285c22d528440e173fdb65ad1052d96df3c),
|
||||
[#4989](https://github.com/angular/angular.js/issues/4989), [#5089](https://github.com/angular/angular.js/issues/5089), [#5118](https://github.com/angular/angular.js/issues/5118), [#5580](https://github.com/angular/angular.js/issues/5580))
|
||||
- re-assign history after BFCache back on Android browser
|
||||
([bddd46c8](https://github.com/angular/angular.js/commit/bddd46c8ecf49cfe6c999cd6b4a69b7d7e1f9a33),
|
||||
[#5425](https://github.com/angular/angular.js/issues/5425))
|
||||
- **$resource:** prevent URL template from collapsing into an empty string
|
||||
([131e4014](https://github.com/angular/angular.js/commit/131e4014b831ac81b7979c4523da81ebc5861c70),
|
||||
[#5455](https://github.com/angular/angular.js/issues/5455), [#5493](https://github.com/angular/angular.js/issues/5493))
|
||||
- **$sanitize:** consider the `size` attribute as a valid/allowed attribute
|
||||
([056c8493](https://github.com/angular/angular.js/commit/056c8493521988dbb330c6636135b505737da918),
|
||||
[#5522](https://github.com/angular/angular.js/issues/5522))
|
||||
- **Scope:** don't let watch deregistration mess up the dirty-checking digest loop
|
||||
([884ef0db](https://github.com/angular/angular.js/commit/884ef0dbcdfe614cedc824d079361b53e675d033),
|
||||
[#5525](https://github.com/angular/angular.js/issues/5525))
|
||||
- **input:**
|
||||
- use apply on the change event only when one isn't already in progress
|
||||
([a80049fd](https://github.com/angular/angular.js/commit/a80049fd0ac858eeeb645a4209cb2a661d0b4c33),
|
||||
[#5293](https://github.com/angular/angular.js/issues/5293))
|
||||
- prevent double $digest when using jQuery trigger.
|
||||
([1147f219](https://github.com/angular/angular.js/commit/1147f21999edf9a434cd8d24865a6455e744d858),
|
||||
[#5293](https://github.com/angular/angular.js/issues/5293))
|
||||
- **ngRepeat:** allow for more flexible coding style in ngRepeat expression
|
||||
([c9705b75](https://github.com/angular/angular.js/commit/c9705b755645a4bfe066243f2ba15a733c3787e1),
|
||||
[#5537](https://github.com/angular/angular.js/issues/5537), [#5598](https://github.com/angular/angular.js/issues/5598))
|
||||
- **ngRoute:** instantiate controller when template is empty
|
||||
([498365f2](https://github.com/angular/angular.js/commit/498365f219f65d6c29bdf2f03610a4d3646009bb),
|
||||
[#5550](https://github.com/angular/angular.js/issues/5550))
|
||||
- **ngShow/ngHide, ngIf:** functions with zero args should be truthy
|
||||
([01c5be46](https://github.com/angular/angular.js/commit/01c5be4681e34cdc5f5c461b7a618fefe8038919),
|
||||
[#5414](https://github.com/angular/angular.js/issues/5414))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **Scope:** limit propagation of $broadcast to scopes that have listeners for the event
|
||||
([80e7a455](https://github.com/angular/angular.js/commit/80e7a4558490f7ffd33d142844b9153a5ed00e86),
|
||||
[#5341](https://github.com/angular/angular.js/issues/5341), [#5371](https://github.com/angular/angular.js/issues/5371))
|
||||
|
||||
<a name="1.2.6"></a>
|
||||
# 1.2.6 taco-salsafication (2013-12-19)
|
||||
|
||||
@@ -4384,3 +4452,6 @@ with the `$route` service
|
||||
[module]: http://docs-next.angularjs.org/api/angular.mock.module
|
||||
[guide2.di]: http://docs-next.angularjs.org/guide/dev_guide.di
|
||||
[jqLite2]: http://docs.angularjs.org/#!/api/angular.element
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
@@ -258,3 +258,6 @@ You can find out more detailed information about contributing in the
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
@@ -38,3 +38,7 @@ To execute end-to-end (e2e) tests, use:
|
||||
|
||||
To learn more about the grunt tasks, run `grunt --help` and also read our
|
||||
[contribution guidelines](http://docs.angularjs.org/misc/contribute).
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
|
||||
@@ -59,3 +59,5 @@ The following is done automatically and should not be done manually:
|
||||
|
||||
1. Unassign yourself from the issue
|
||||
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $httpBackend:noxhr
|
||||
@fullName Unsupported XHR
|
||||
@description
|
||||
|
||||
This error occurs in browsers that do not support XmlHttpRequest. AngularJS
|
||||
supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers
|
||||
(Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially
|
||||
supported browser.
|
||||
@@ -13,7 +13,7 @@ For example the issue can be triggered by this *invalid* code:
|
||||
<div ng-repeat="value in [4, 4]"></div>
|
||||
```
|
||||
|
||||
To resolve this error either ensure that the items in the collection have unique identity of use the `track by` syntax to specify how to track the association between models and DOM.
|
||||
To resolve this error either ensure that the items in the collection have unique identity or use the `track by` syntax to specify how to track the association between models and DOM.
|
||||
|
||||
To resolve the example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ function MyClass(xhr) {
|
||||
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
|
||||
instead about whoever created the class responsible for passing it in. Since the creator of the
|
||||
class should be different code than the user of the class, it separates the responsibility of
|
||||
creation from the logic. This is dependency-injection is in a nutshell.
|
||||
creation from the logic. This is dependency-injection in a nutshell.
|
||||
|
||||
The class above is testable, since in the test we can write:
|
||||
<pre>
|
||||
|
||||
@@ -33,10 +33,10 @@ In addition it provides an {@link api/ng.directive:ngModel.NgModelController API
|
||||
|
||||
<script>
|
||||
function Controller($scope) {
|
||||
$scope.master= {};
|
||||
$scope.master = {};
|
||||
|
||||
$scope.update = function(user) {
|
||||
$scope.master= angular.copy(user);
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
@@ -235,7 +235,7 @@ In the following example we create two directives.
|
||||
<script>
|
||||
var app = angular.module('form-example1', []);
|
||||
|
||||
var INTEGER_REGEXP = /^\-?\d*$/;
|
||||
var INTEGER_REGEXP = /^\-?\d+$/;
|
||||
app.directive('integer', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"version": "1.2.6",
|
||||
"cdnVersion": "1.2.5",
|
||||
"codename": "taco-salsafication",
|
||||
"version": "1.2.7",
|
||||
"cdnVersion": "1.2.6",
|
||||
"codename": "emoji-clairvoyance",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -20,13 +20,13 @@
|
||||
"q-io": "~1.10.6",
|
||||
"qq": "~0.3.5",
|
||||
"shelljs": "~0.2.6",
|
||||
"karma": "~0.11",
|
||||
"karma": "0.11.11",
|
||||
"karma-jasmine": "~0.1.0",
|
||||
"karma-chrome-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.0",
|
||||
"karma-ng-scenario": "~0.1.0",
|
||||
"karma-junit-reporter": "~0.2.1",
|
||||
"karma-sauce-launcher": "~0.1.1",
|
||||
"karma-sauce-launcher": "~0.2.0",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"yaml-js": "~0.0.8",
|
||||
"marked": "0.2.9",
|
||||
|
||||
@@ -32,8 +32,10 @@ cd `dirname $0`/../..
|
||||
./scripts/jenkins/bump-increment.sh $BUMP_TYPE
|
||||
|
||||
echo "-- push to Github"
|
||||
# push to github
|
||||
git push --all
|
||||
# push the commits to github
|
||||
git push origin master
|
||||
# push the release tag
|
||||
git push origin v`cat build/version.txt`
|
||||
|
||||
# Update code.angularjs.org
|
||||
./scripts/code.angularjs.org/publish.sh
|
||||
|
||||
+3
-1
@@ -959,7 +959,9 @@ function fromJson(json) {
|
||||
|
||||
|
||||
function toBoolean(value) {
|
||||
if (value && value.length !== 0) {
|
||||
if (typeof value === 'function') {
|
||||
value = true;
|
||||
} else if (value && value.length !== 0) {
|
||||
var v = lowercase("" + value);
|
||||
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
|
||||
} else {
|
||||
|
||||
@@ -740,6 +740,11 @@ function createInjector(modulesToLoad) {
|
||||
path.unshift(serviceName);
|
||||
cache[serviceName] = INSTANTIATING;
|
||||
return cache[serviceName] = factory(serviceName);
|
||||
} catch (err) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
delete cache[serviceName];
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
path.shift();
|
||||
}
|
||||
|
||||
+7
-6
@@ -148,8 +148,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* @param {boolean=} replace Should new url replace current history record ?
|
||||
*/
|
||||
self.url = function(url, replace) {
|
||||
// Android Browser BFCache causes location reference to become stale.
|
||||
// Android Browser BFCache causes location, history reference to become stale.
|
||||
if (location !== window.location) location = window.location;
|
||||
if (history !== window.history) history = window.history;
|
||||
|
||||
// setter
|
||||
if (url) {
|
||||
@@ -201,7 +202,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* @description
|
||||
* Register callback function that will be called, when url changes.
|
||||
*
|
||||
* It's only called when the url is changed by outside of angular:
|
||||
* It's only called when the url is changed from outside of angular:
|
||||
* - user types different url into address bar
|
||||
* - user clicks on history (forward/back) button
|
||||
* - user clicks on a link
|
||||
@@ -243,7 +244,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
/**
|
||||
* @name ng.$browser#baseHref
|
||||
* @methodOf ng.$browser
|
||||
*
|
||||
*
|
||||
* @description
|
||||
* Returns current <base href>
|
||||
* (always relative - without domain)
|
||||
@@ -252,7 +253,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
*/
|
||||
self.baseHref = function() {
|
||||
var href = baseElement.attr('href');
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
|
||||
return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -274,13 +275,13 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* It is not meant to be used directly, use the $cookie service instead.
|
||||
*
|
||||
* The return values vary depending on the arguments that the method was called with as follows:
|
||||
*
|
||||
*
|
||||
* - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
|
||||
* it
|
||||
* - cookies(name, value) -> set name to value, if value is undefined delete the cookie
|
||||
* - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
|
||||
* way)
|
||||
*
|
||||
*
|
||||
* @returns {Object} Hash of all cookies (if called without any parameter)
|
||||
*/
|
||||
self.cookies = function(name, value) {
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
|
||||
* Compiles an HTML string or DOM into a template and produces a template function, which
|
||||
* can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
|
||||
*
|
||||
* The compilation is a process of walking the DOM tree and matching DOM elements to
|
||||
|
||||
@@ -419,9 +419,13 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (ctrl.$viewValue !== value) {
|
||||
scope.$apply(function() {
|
||||
if (scope.$$phase) {
|
||||
ctrl.$setViewValue(value);
|
||||
});
|
||||
} else {
|
||||
scope.$apply(function() {
|
||||
ctrl.$setViewValue(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
$$tlb: true,
|
||||
link: function($scope, $element, $attr, ctrl, $transclude){
|
||||
var expression = $attr.ngRepeat;
|
||||
var match = expression.match(/^\s*(.+)\s+in\s+([\r\n\s\S]*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
|
||||
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),
|
||||
trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
|
||||
lhs, rhs, valueIdentifier, keyIdentifier,
|
||||
hashFnLocals = {$id: hashKey};
|
||||
@@ -215,7 +215,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
|
||||
lhs = match[1];
|
||||
rhs = match[2];
|
||||
trackByExp = match[4];
|
||||
trackByExp = match[3];
|
||||
|
||||
if (trackByExp) {
|
||||
trackByExpGetter = $parse(trackByExp);
|
||||
|
||||
@@ -25,21 +25,21 @@
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above.
|
||||
*
|
||||
* - `function`: A predicate function can be used to write arbitrary filters. The function is
|
||||
* - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
|
||||
* called for each element of `array`. The final result is an array of those elements that
|
||||
* the predicate returned true for.
|
||||
*
|
||||
* @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
|
||||
* @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
|
||||
* determining if the expected value (from the filter expression) and actual value (from
|
||||
* the object in the array) should be considered a match.
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `function(expected, actual)`:
|
||||
* - `function(actual, expected)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
*
|
||||
* - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
|
||||
+5
-4
@@ -222,7 +222,7 @@ function $HttpProvider() {
|
||||
* will result in the success callback being called. Note that if the response is a redirect,
|
||||
* XMLHttpRequest will transparently follow it, meaning that the error callback will not be
|
||||
* called for such responses.
|
||||
*
|
||||
*
|
||||
* # Calling $http from outside AngularJS
|
||||
* The `$http` service will not actually send the request until the next `$digest()` is
|
||||
* executed. Normally this is not an issue, since almost all the time your call to `$http` will
|
||||
@@ -409,19 +409,20 @@ function $HttpProvider() {
|
||||
* return responseOrNewPromise
|
||||
* }
|
||||
* return $q.reject(rejection);
|
||||
* };
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
* });
|
||||
*
|
||||
* $httpProvider.interceptors.push('myHttpInterceptor');
|
||||
*
|
||||
*
|
||||
* // register the interceptor via an anonymous factory
|
||||
* // alternatively, register the interceptor via an anonymous factory
|
||||
* $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
|
||||
* return {
|
||||
* 'request': function(config) {
|
||||
* // same as above
|
||||
* },
|
||||
*
|
||||
* 'response': function(response) {
|
||||
* // same as above
|
||||
* }
|
||||
|
||||
+19
-10
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var XHR = window.XMLHttpRequest || function() {
|
||||
function createXhr(method) {
|
||||
// IE8 doesn't support PATCH method, but the ActiveX object does
|
||||
/* global ActiveXObject */
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
|
||||
throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
|
||||
};
|
||||
return (msie <= 8 && lowercase(method) === 'patch')
|
||||
? new ActiveXObject('Microsoft.XMLHTTP')
|
||||
: new window.XMLHttpRequest();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,11 +28,11 @@ var XHR = window.XMLHttpRequest || function() {
|
||||
*/
|
||||
function $HttpBackendProvider() {
|
||||
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
|
||||
return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
|
||||
return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]);
|
||||
}];
|
||||
}
|
||||
|
||||
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
|
||||
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
|
||||
var ABORTED = -1;
|
||||
|
||||
// TODO(vojta): fix the signature
|
||||
@@ -57,7 +57,9 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
|
||||
delete callbacks[callbackId];
|
||||
});
|
||||
} else {
|
||||
var xhr = new XHR();
|
||||
|
||||
var xhr = createXhr(method);
|
||||
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
if (isDefined(value)) {
|
||||
@@ -69,7 +71,14 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
|
||||
// response is in the cache. the promise api will ensure that to the app code the api is
|
||||
// always async
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
// onreadystatechange might by called multiple times with readyState === 4 on mobile webkit caused by
|
||||
// xhrs that are resolved while the app is in the background (see #5426).
|
||||
// since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
|
||||
// continuing
|
||||
//
|
||||
// we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
|
||||
// Safari respectively.
|
||||
if (xhr && xhr.readyState == 4) {
|
||||
var responseHeaders = null,
|
||||
response = null;
|
||||
|
||||
|
||||
+7
-6
@@ -659,16 +659,17 @@ function $LocationProvider(){
|
||||
// update $location when $browser url changes
|
||||
$browser.onUrlChange(function(newUrl) {
|
||||
if ($location.absUrl() != newUrl) {
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl,
|
||||
$location.absUrl()).defaultPrevented) {
|
||||
$browser.url($location.absUrl());
|
||||
return;
|
||||
}
|
||||
$rootScope.$evalAsync(function() {
|
||||
var oldUrl = $location.absUrl();
|
||||
|
||||
$location.$$parse(newUrl);
|
||||
afterLocationChange(oldUrl);
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl,
|
||||
oldUrl).defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$browser.url(oldUrl);
|
||||
} else {
|
||||
afterLocationChange(oldUrl);
|
||||
}
|
||||
});
|
||||
if (!$rootScope.$$phase) $rootScope.$digest();
|
||||
}
|
||||
|
||||
+30
-4
@@ -133,6 +133,7 @@ function $RootScopeProvider(){
|
||||
this.$$asyncQueue = [];
|
||||
this.$$postDigestQueue = [];
|
||||
this.$$listeners = {};
|
||||
this.$$listenerCount = {};
|
||||
this.$$isolateBindings = {};
|
||||
}
|
||||
|
||||
@@ -192,6 +193,7 @@ function $RootScopeProvider(){
|
||||
}
|
||||
child['this'] = child;
|
||||
child.$$listeners = {};
|
||||
child.$$listenerCount = {};
|
||||
child.$parent = this;
|
||||
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
||||
child.$$prevSibling = this.$$childTail;
|
||||
@@ -351,6 +353,7 @@ function $RootScopeProvider(){
|
||||
|
||||
return function() {
|
||||
arrayRemove(array, watcher);
|
||||
lastDirtyWatch = null;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -696,6 +699,8 @@ function $RootScopeProvider(){
|
||||
this.$$destroyed = true;
|
||||
if (this === $rootScope) return;
|
||||
|
||||
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
|
||||
|
||||
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
||||
@@ -885,8 +890,18 @@ function $RootScopeProvider(){
|
||||
}
|
||||
namedListeners.push(listener);
|
||||
|
||||
var current = this;
|
||||
do {
|
||||
if (!current.$$listenerCount[name]) {
|
||||
current.$$listenerCount[name] = 0;
|
||||
}
|
||||
current.$$listenerCount[name]++;
|
||||
} while ((current = current.$parent));
|
||||
|
||||
var self = this;
|
||||
return function() {
|
||||
namedListeners[indexOf(namedListeners, listener)] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
};
|
||||
},
|
||||
|
||||
@@ -998,8 +1013,7 @@ function $RootScopeProvider(){
|
||||
listeners, i, length;
|
||||
|
||||
//down while you can, then up and next sibling or up and next sibling until back at root
|
||||
do {
|
||||
current = next;
|
||||
while ((current = next)) {
|
||||
event.currentScope = current;
|
||||
listeners = current.$$listeners[name] || [];
|
||||
for (i=0, length = listeners.length; i<length; i++) {
|
||||
@@ -1021,12 +1035,14 @@ function $RootScopeProvider(){
|
||||
// Insanity Warning: scope depth-first traversal
|
||||
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
||||
// this piece should be kept in sync with the traversal in $digest
|
||||
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
|
||||
// (though it differs due to having the extra check for $$listenerCount)
|
||||
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
|
||||
(current !== target && current.$$nextSibling)))) {
|
||||
while(current !== target && !(next = current.$$nextSibling)) {
|
||||
current = current.$parent;
|
||||
}
|
||||
}
|
||||
} while ((current = next));
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
@@ -1055,6 +1071,16 @@ function $RootScopeProvider(){
|
||||
return fn;
|
||||
}
|
||||
|
||||
function decrementListenerCount(current, count, name) {
|
||||
do {
|
||||
current.$$listenerCount[name] -= count;
|
||||
|
||||
if (current.$$listenerCount[name] === 0) {
|
||||
delete current.$$listenerCount[name];
|
||||
}
|
||||
} while ((current = current.$parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* function used as an initial value for watchers.
|
||||
* because it's unique we can easily tell it apart from other values
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ function $SnifferProvider() {
|
||||
// http://code.google.com/p/android/issues/detail?id=17471
|
||||
// https://github.com/angular/angular.js/issues/904
|
||||
|
||||
// older webit browser (533.9) on Boxee box has exactly the same problem as Android has
|
||||
// older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
|
||||
// so let's not use the history API also
|
||||
// We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
|
||||
// jshint -W018
|
||||
|
||||
@@ -610,9 +610,14 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
var animations = [];
|
||||
|
||||
//only add animations if the currently running animation is not structural
|
||||
//or if there is no animation running at all
|
||||
if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) {
|
||||
var allowAnimations = isClassBased ?
|
||||
!ngAnimateState.disabled && (!ngAnimateState.running || !ngAnimateState.structural) :
|
||||
true;
|
||||
|
||||
if(allowAnimations) {
|
||||
forEach(matches, function(animation) {
|
||||
//add the animation to the queue to if it is allowed to be cancelled
|
||||
if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) {
|
||||
@@ -1141,7 +1146,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var propertyStyle = timings.transitionPropertyStyle;
|
||||
if(propertyStyle.indexOf('all') == -1) {
|
||||
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
|
||||
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + 's;';
|
||||
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-property');
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-duration');
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -1572,6 +1572,10 @@ function MockHttpExpectation(method, url, data, headers) {
|
||||
};
|
||||
}
|
||||
|
||||
function createMockXhr() {
|
||||
return new MockXhr();
|
||||
}
|
||||
|
||||
function MockXhr() {
|
||||
|
||||
// hack for testing $http, $httpBackend
|
||||
|
||||
@@ -401,7 +401,7 @@ angular.module('ngResource', ['ng']).
|
||||
});
|
||||
|
||||
// strip trailing slashes and set the url
|
||||
url = url.replace(/\/+$/, '');
|
||||
url = url.replace(/\/+$/, '') || '/';
|
||||
// then replace collapse `/.` if found in the last URL path segment before the query
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
|
||||
|
||||
@@ -199,7 +199,7 @@ function ngViewFactory( $route, $anchorScroll, $animate) {
|
||||
var locals = $route.current && $route.current.locals,
|
||||
template = locals && locals.$template;
|
||||
|
||||
if (template) {
|
||||
if (angular.isDefined(template)) {
|
||||
var newScope = scope.$new();
|
||||
var current = $route.current;
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
|
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
||||
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
|
||||
'valign,value,vspace,width'));
|
||||
|
||||
function makeMap(str) {
|
||||
|
||||
@@ -239,7 +239,8 @@ function callerFile(offset) {
|
||||
* To work around this we instead use our own handler that fires a real event.
|
||||
*/
|
||||
(function(fn){
|
||||
var parentTrigger = fn.trigger;
|
||||
// We need a handle to the original trigger function for input tests.
|
||||
var parentTrigger = fn._originalTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keydown|blur|input|mousedown|mouseup)/.test(type)) {
|
||||
var processDefaults = [];
|
||||
|
||||
@@ -74,6 +74,17 @@ describe('injector', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should not corrupt the cache when an object fails to get instantiated', function() {
|
||||
expect(function() {
|
||||
injector.get('idontexist');
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist");
|
||||
|
||||
expect(function() {
|
||||
injector.get('idontexist');
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist");
|
||||
});
|
||||
|
||||
|
||||
it('should provide path to the missing provider', function() {
|
||||
providers('a', function(idontexist) {return 1;});
|
||||
providers('b', function(a) {return 2;});
|
||||
|
||||
@@ -609,5 +609,10 @@ describe('browser', function() {
|
||||
fakeDocument.basePath = 'http://host.com/base/path/index.html';
|
||||
expect(browser.baseHref()).toEqual('/base/path/index.html');
|
||||
});
|
||||
|
||||
it('should remove domain from <base href> beginning with \'//\'', function() {
|
||||
fakeDocument.basePath = '//google.com/base/path/';
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,9 +202,14 @@ describe('$compile', function() {
|
||||
if (msie < 9) return;
|
||||
|
||||
element = jqLite('<div>{{1+2}}</div>');
|
||||
element[0].childNodes[1] = {nodeType: 3, nodeName: 'OBJECT', textContent: 'fake node'};
|
||||
|
||||
if (!element[0].childNodes[1]) return; //browser doesn't support this kind of mocking
|
||||
try {
|
||||
element[0].childNodes[1] = {nodeType: 3, nodeName: 'OBJECT', textContent: 'fake node'};
|
||||
} catch(e) {
|
||||
} finally {
|
||||
if (!element[0].childNodes[1]) return; //browser doesn't support this kind of mocking
|
||||
}
|
||||
|
||||
expect(element[0].childNodes[1].textContent).toBe('fake node');
|
||||
|
||||
$compile(element)($rootScope);
|
||||
@@ -4243,7 +4248,7 @@ describe('$compile', function() {
|
||||
expect(element.attr('test2')).toBe('Misko');
|
||||
expect(element.attr('test3')).toBe('Misko');
|
||||
}));
|
||||
|
||||
|
||||
it('should work with the "href" attribute', inject(function($compile, $rootScope) {
|
||||
$rootScope.value = 'test';
|
||||
element = $compile('<a ng-attr-href="test/{{value}}"></a>')($rootScope);
|
||||
|
||||
@@ -533,6 +533,23 @@ describe('input', function() {
|
||||
'event so that form auto complete works',function() {
|
||||
assertBrowserSupportsChangeEvent(true);
|
||||
});
|
||||
|
||||
if (!_jqLiteMode) {
|
||||
it('should not cause the double $digest when triggering an event using jQuery', function() {
|
||||
$sniffer.hasEvent = function(eventName) {
|
||||
return eventName !== 'input';
|
||||
};
|
||||
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
|
||||
|
||||
scope.field = 'fake field';
|
||||
scope.$watch('field', function() {
|
||||
// We need to use _originalTrigger since trigger is modified by Angular Scenario.
|
||||
inputElm._originalTrigger('change');
|
||||
});
|
||||
scope.$apply();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('"paste" and "cut" events', function() {
|
||||
|
||||
@@ -177,22 +177,6 @@ describe('ngRepeat', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should allow expressions over multiple lines', function() {
|
||||
scope.isTrue = function() {
|
||||
return true;
|
||||
};
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="item in items\n' +
|
||||
'| filter:isTrue">{{item.name}}</li>' +
|
||||
'</ul>')(scope);
|
||||
scope.items = [{name: 'igor'}];
|
||||
scope.$digest();
|
||||
|
||||
expect(element.find('li').text()).toBe('igor');
|
||||
});
|
||||
|
||||
|
||||
it('should track using provided function when a filter is present', function() {
|
||||
scope.newArray = function (items) {
|
||||
var newArray = [];
|
||||
@@ -354,6 +338,38 @@ describe('ngRepeat', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should allow expressions over multiple lines', function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="item in items\n' +
|
||||
'| filter:isTrue">{{item.name}}/</li>' +
|
||||
'</ul>')(scope);
|
||||
|
||||
scope.isTrue = function() {return true;};
|
||||
scope.items = [{name: 'igor'}, {name: 'misko'}];
|
||||
|
||||
scope.$digest();
|
||||
|
||||
expect(element.text()).toEqual('igor/misko/');
|
||||
});
|
||||
|
||||
|
||||
it('should strip white space characters correctly', function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="item \t\n \t in \n \t\n\n \nitems \t\t\n | filter:\n\n{' +
|
||||
'\n\t name:\n\n \'ko\'\n\n}\n\n | orderBy: \t \n \'name\' \n\n' +
|
||||
'track \t\n by \n\n\t $index \t\n ">{{item.name}}/</li>' +
|
||||
'</ul>')(scope);
|
||||
|
||||
scope.items = [{name: 'igor'}, {name: 'misko'}];
|
||||
|
||||
scope.$digest();
|
||||
|
||||
expect(element.text()).toEqual('misko/');
|
||||
});
|
||||
|
||||
|
||||
it('should not ngRepeat over parent properties', function() {
|
||||
var Class = function() {};
|
||||
Class.prototype.abc = function() {};
|
||||
|
||||
@@ -20,6 +20,16 @@ describe('ngShow / ngHide', function() {
|
||||
}));
|
||||
|
||||
|
||||
// https://github.com/angular/angular.js/issues/5414
|
||||
it('should show if the expression is a function with a no arguments', inject(function($rootScope, $compile) {
|
||||
element = jqLite('<div ng-show="exp"></div>');
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.exp = function(){};
|
||||
$rootScope.$digest();
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
||||
|
||||
it('should make hidden element visible', inject(function($rootScope, $compile) {
|
||||
element = jqLite('<div class="ng-hide" ng-show="exp"></div>');
|
||||
element = $compile(element)($rootScope);
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('$httpBackend', function() {
|
||||
})
|
||||
}
|
||||
};
|
||||
$backend = createHttpBackend($browser, MockXhr, fakeTimeout, callbacks, fakeDocument);
|
||||
$backend = createHttpBackend($browser, createMockXhr, fakeTimeout, callbacks, fakeDocument);
|
||||
callback = jasmine.createSpy('done');
|
||||
}));
|
||||
|
||||
@@ -90,6 +90,20 @@ describe('$httpBackend', function() {
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
// onreadystatechange might by called multiple times
|
||||
// with readyState === 4 on mobile webkit caused by
|
||||
// xhrs that are resolved while the app is in the background (see #5426).
|
||||
it('should not process onreadystatechange callback with readyState == 4 more than once', function() {
|
||||
$backend('GET', 'URL', null, callback);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
|
||||
xhr.status = 200;
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
xhr.onreadystatechange();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should set only the requested headers', function() {
|
||||
$backend('POST', 'URL', null, noop, {'X-header1': 'value1', 'X-header2': 'value2'});
|
||||
@@ -238,7 +252,7 @@ describe('$httpBackend', function() {
|
||||
expect(response).toBe('response');
|
||||
});
|
||||
|
||||
$backend = createHttpBackend($browser, SyncXhr);
|
||||
$backend = createHttpBackend($browser, function() { return new SyncXhr() });
|
||||
$backend('GET', '/url', null, callback);
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
@@ -414,7 +428,7 @@ describe('$httpBackend', function() {
|
||||
|
||||
|
||||
it('should convert 0 to 200 if content', function() {
|
||||
$backend = createHttpBackend($browser, MockXhr);
|
||||
$backend = createHttpBackend($browser, createMockXhr);
|
||||
|
||||
$backend('GET', 'file:///whatever/index.html', null, callback);
|
||||
respond(0, 'SOME CONTENT');
|
||||
@@ -425,7 +439,7 @@ describe('$httpBackend', function() {
|
||||
|
||||
|
||||
it('should convert 0 to 404 if no content', function() {
|
||||
$backend = createHttpBackend($browser, MockXhr);
|
||||
$backend = createHttpBackend($browser, createMockXhr);
|
||||
|
||||
$backend('GET', 'file:///whatever/index.html', null, callback);
|
||||
respond(0, '');
|
||||
@@ -453,7 +467,7 @@ describe('$httpBackend', function() {
|
||||
|
||||
try {
|
||||
|
||||
$backend = createHttpBackend($browser, MockXhr);
|
||||
$backend = createHttpBackend($browser, createMockXhr);
|
||||
|
||||
$backend('GET', '/whatever/index.html', null, callback);
|
||||
respond(0, '');
|
||||
@@ -468,7 +482,7 @@ describe('$httpBackend', function() {
|
||||
|
||||
|
||||
it('should return original backend status code if different from 0', function () {
|
||||
$backend = createHttpBackend($browser, MockXhr);
|
||||
$backend = createHttpBackend($browser, createMockXhr);
|
||||
|
||||
// request to http://
|
||||
$backend('POST', 'http://rest_api/create_whatever', null, callback);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
describe('$location', function() {
|
||||
var url;
|
||||
|
||||
beforeEach(module(provideLog));
|
||||
|
||||
afterEach(function() {
|
||||
// link rewriting used in html5 mode on legacy browsers binds to document.onClick, so we need
|
||||
// to clean this up after each test.
|
||||
@@ -1401,6 +1403,32 @@ describe('$location', function() {
|
||||
dealoc($rootElement);
|
||||
});
|
||||
});
|
||||
|
||||
it('should always return the new url value via path() when $locationChangeStart event occurs regardless of cause',
|
||||
inject(function($location, $rootScope, $browser, log) {
|
||||
var base = $browser.url();
|
||||
|
||||
$rootScope.$on('$locationChangeStart', function() {
|
||||
log($location.path());
|
||||
});
|
||||
|
||||
// change through $location service
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/myNewPath');
|
||||
});
|
||||
|
||||
// reset location
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('');
|
||||
});
|
||||
|
||||
// change through $browser
|
||||
$browser.url(base + '#/myNewPath');
|
||||
$browser.poll();
|
||||
|
||||
expect(log).toEqual(['/myNewPath', '/', '/myNewPath']);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('LocationHtml5Url', function() {
|
||||
|
||||
+195
-54
@@ -323,41 +323,6 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should return a function that allows listeners to be unregistered', inject(
|
||||
function($rootScope) {
|
||||
var listener = jasmine.createSpy('watch listener'),
|
||||
listenerRemove;
|
||||
|
||||
listenerRemove = $rootScope.$watch('foo', listener);
|
||||
$rootScope.$digest(); //init
|
||||
expect(listener).toHaveBeenCalled();
|
||||
expect(listenerRemove).toBeDefined();
|
||||
|
||||
listener.reset();
|
||||
$rootScope.foo = 'bar';
|
||||
$rootScope.$digest(); //triger
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
|
||||
listener.reset();
|
||||
$rootScope.foo = 'baz';
|
||||
listenerRemove();
|
||||
$rootScope.$digest(); //trigger
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should allow a watch to be unregistered while in a digest', inject(function($rootScope) {
|
||||
var remove1, remove2;
|
||||
$rootScope.$watch('remove', function() {
|
||||
remove1();
|
||||
remove2();
|
||||
});
|
||||
remove1 = $rootScope.$watch('thing', function() {});
|
||||
remove2 = $rootScope.$watch('thing', function() {});
|
||||
expect(function() {
|
||||
$rootScope.$apply('remove = true');
|
||||
}).not.toThrow();
|
||||
}));
|
||||
|
||||
it('should allow a watch to be added while in a digest', inject(function($rootScope) {
|
||||
var watch1 = jasmine.createSpy('watch1'),
|
||||
watch2 = jasmine.createSpy('watch2');
|
||||
@@ -402,6 +367,94 @@ describe('Scope', function() {
|
||||
expect(log).toEqual([]);
|
||||
}));
|
||||
|
||||
|
||||
describe('$watch deregistration', function() {
|
||||
|
||||
it('should return a function that allows listeners to be deregistered', inject(
|
||||
function($rootScope) {
|
||||
var listener = jasmine.createSpy('watch listener'),
|
||||
listenerRemove;
|
||||
|
||||
listenerRemove = $rootScope.$watch('foo', listener);
|
||||
$rootScope.$digest(); //init
|
||||
expect(listener).toHaveBeenCalled();
|
||||
expect(listenerRemove).toBeDefined();
|
||||
|
||||
listener.reset();
|
||||
$rootScope.foo = 'bar';
|
||||
$rootScope.$digest(); //triger
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
|
||||
listener.reset();
|
||||
$rootScope.foo = 'baz';
|
||||
listenerRemove();
|
||||
$rootScope.$digest(); //trigger
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should allow a watch to be deregistered while in a digest', inject(function($rootScope) {
|
||||
var remove1, remove2;
|
||||
$rootScope.$watch('remove', function() {
|
||||
remove1();
|
||||
remove2();
|
||||
});
|
||||
remove1 = $rootScope.$watch('thing', function() {});
|
||||
remove2 = $rootScope.$watch('thing', function() {});
|
||||
expect(function() {
|
||||
$rootScope.$apply('remove = true');
|
||||
}).not.toThrow();
|
||||
}));
|
||||
|
||||
|
||||
it('should not mess up the digest loop if deregistration happens during digest', inject(
|
||||
function($rootScope, log) {
|
||||
|
||||
// we are testing this due to regression #5525 which is related to how the digest loops lastDirtyWatch
|
||||
// short-circuiting optimization works
|
||||
|
||||
// scenario: watch1 deregistering watch1
|
||||
var scope = $rootScope.$new();
|
||||
var deregWatch1 = scope.$watch(log.fn('watch1'), function() { deregWatch1(); log('watchAction1'); });
|
||||
scope.$watch(log.fn('watch2'), log.fn('watchAction2'));
|
||||
scope.$watch(log.fn('watch3'), log.fn('watchAction3'));
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(log).toEqual(['watch1', 'watchAction1', 'watch2', 'watchAction2', 'watch3', 'watchAction3',
|
||||
'watch2', 'watch3']);
|
||||
scope.$destroy();
|
||||
log.reset();
|
||||
|
||||
|
||||
// scenario: watch1 deregistering watch2
|
||||
scope = $rootScope.$new();
|
||||
scope.$watch(log.fn('watch1'), function() { deregWatch2(); log('watchAction1'); });
|
||||
var deregWatch2 = scope.$watch(log.fn('watch2'), log.fn('watchAction2'));
|
||||
scope.$watch(log.fn('watch3'), log.fn('watchAction3'));
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(log).toEqual(['watch1', 'watchAction1', 'watch1', 'watch3', 'watchAction3',
|
||||
'watch1', 'watch3']);
|
||||
scope.$destroy();
|
||||
log.reset();
|
||||
|
||||
|
||||
// scenario: watch2 deregistering watch1
|
||||
scope = $rootScope.$new();
|
||||
deregWatch1 = scope.$watch(log.fn('watch1'), log.fn('watchAction1'));
|
||||
scope.$watch(log.fn('watch2'), function() { deregWatch1(); log('watchAction2'); });
|
||||
scope.$watch(log.fn('watch3'), log.fn('watchAction3'));
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(log).toEqual(['watch1', 'watchAction1', 'watch2', 'watchAction2', 'watch3', 'watchAction3',
|
||||
'watch2', 'watch3']);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('$watchCollection', function() {
|
||||
var log, $rootScope, deregister;
|
||||
|
||||
@@ -730,6 +783,28 @@ describe('Scope', function() {
|
||||
first.$apply();
|
||||
expect(log).toBe('1232323');
|
||||
}));
|
||||
|
||||
|
||||
it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) {
|
||||
var EVENT = 'fooEvent',
|
||||
spy = jasmine.createSpy('listener'),
|
||||
firstSecond = first.$new();
|
||||
|
||||
firstSecond.$on(EVENT, spy);
|
||||
firstSecond.$on(EVENT, spy);
|
||||
middle.$on(EVENT, spy);
|
||||
|
||||
expect($rootScope.$$listenerCount[EVENT]).toBe(3);
|
||||
expect(first.$$listenerCount[EVENT]).toBe(2);
|
||||
|
||||
firstSecond.$destroy();
|
||||
|
||||
expect($rootScope.$$listenerCount[EVENT]).toBe(1);
|
||||
expect(first.$$listenerCount[EVENT]).toBeUndefined();
|
||||
|
||||
$rootScope.$broadcast(EVENT);
|
||||
expect(spy.callCount).toBe(1);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -1091,29 +1166,78 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should return a function that deregisters the listener', inject(function($rootScope) {
|
||||
var log = '',
|
||||
child = $rootScope.$new(),
|
||||
listenerRemove;
|
||||
it('should increment ancestor $$listenerCount entries', inject(function($rootScope) {
|
||||
var child1 = $rootScope.$new(),
|
||||
child2 = child1.$new(),
|
||||
spy = jasmine.createSpy();
|
||||
|
||||
function eventFn() {
|
||||
log += 'X';
|
||||
}
|
||||
$rootScope.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
listenerRemove = child.$on('abc', eventFn);
|
||||
expect(log).toEqual('');
|
||||
expect(listenerRemove).toBeDefined();
|
||||
child1.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('XX');
|
||||
|
||||
log = '';
|
||||
listenerRemove();
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('');
|
||||
child2.$on('event2', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1});
|
||||
expect(child2.$$listenerCount).toEqual({event2: 1});
|
||||
}));
|
||||
|
||||
|
||||
describe('deregistration', function() {
|
||||
|
||||
it('should return a function that deregisters the listener', inject(function($rootScope) {
|
||||
var log = '',
|
||||
child = $rootScope.$new(),
|
||||
listenerRemove;
|
||||
|
||||
function eventFn() {
|
||||
log += 'X';
|
||||
}
|
||||
|
||||
listenerRemove = child.$on('abc', eventFn);
|
||||
expect(log).toEqual('');
|
||||
expect(listenerRemove).toBeDefined();
|
||||
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('XX');
|
||||
expect($rootScope.$$listenerCount['abc']).toBe(1);
|
||||
|
||||
log = '';
|
||||
listenerRemove();
|
||||
child.$emit('abc');
|
||||
child.$broadcast('abc');
|
||||
expect(log).toEqual('');
|
||||
expect($rootScope.$$listenerCount['abc']).toBeUndefined();
|
||||
}));
|
||||
|
||||
|
||||
it('should decrement ancestor $$listenerCount entries', inject(function($rootScope) {
|
||||
var child1 = $rootScope.$new(),
|
||||
child2 = child1.$new(),
|
||||
spy = jasmine.createSpy();
|
||||
|
||||
$rootScope.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
child1.$on('event1', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
|
||||
var deregisterEvent2Listener = child2.$on('event2', spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1});
|
||||
expect(child2.$$listenerCount).toEqual({event2: 1});
|
||||
|
||||
deregisterEvent2Listener();
|
||||
|
||||
expect($rootScope.$$listenerCount).toEqual({event1: 2});
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
expect(child2.$$listenerCount).toEqual({});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1360,6 +1484,23 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not descend past scopes with a $$listerCount of 0 or undefined',
|
||||
inject(function($rootScope) {
|
||||
var EVENT = 'fooEvent',
|
||||
spy = jasmine.createSpy('listener');
|
||||
|
||||
// Precondition: There should be no listeners for fooEvent.
|
||||
expect($rootScope.$$listenerCount[EVENT]).toBeUndefined();
|
||||
|
||||
// Add a spy listener to a child scope.
|
||||
$rootScope.$$childHead.$$listeners[EVENT] = [spy];
|
||||
|
||||
// $rootScope's count for 'fooEvent' is undefined, so spy should not be called.
|
||||
$rootScope.$broadcast(EVENT);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should return event object', function() {
|
||||
var result = child1.$broadcast('some');
|
||||
|
||||
|
||||
@@ -538,6 +538,27 @@ describe("ngAnimate", function() {
|
||||
expect(completed).toBe(true);
|
||||
}));
|
||||
|
||||
it("should skip class-based animations if animations are directly disabled on the same element", function() {
|
||||
var capture;
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.register('.capture', function() {
|
||||
return {
|
||||
addClass : function(element, className, done) {
|
||||
capture = true;
|
||||
done();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($animate, $rootScope, $sniffer, $timeout) {
|
||||
$animate.enabled(true);
|
||||
$animate.enabled(false, element);
|
||||
|
||||
$animate.addClass(element, 'capture');
|
||||
expect(element.hasClass('capture')).toBe(true);
|
||||
expect(capture).not.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fire the cancel/end function with the correct flag in the parameters",
|
||||
inject(function($animate, $rootScope, $sniffer, $timeout) {
|
||||
@@ -1126,6 +1147,7 @@ describe("ngAnimate", function() {
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
|
||||
expect(elements[0].attr('style')).toMatch(/transition-duration: 1\d*s,\s*3\d*s;/);
|
||||
expect(elements[0].attr('style')).not.toContain('transition-delay');
|
||||
expect(elements[1].attr('style')).toMatch(/transition-delay: 2\.1\d*s,\s*4\.1\d*s/);
|
||||
expect(elements[2].attr('style')).toMatch(/transition-delay: 2\.2\d*s,\s*4\.2\d*s/);
|
||||
|
||||
@@ -150,6 +150,13 @@ describe("resource", function() {
|
||||
R.get({a:6, b:7, c:8});
|
||||
});
|
||||
|
||||
it('should not collapsed the url into an empty string', function() {
|
||||
var R = $resource('/:foo/:bar/');
|
||||
|
||||
$httpBackend.when('GET', '/').respond('{}');
|
||||
|
||||
R.get({});
|
||||
});
|
||||
|
||||
it('should support escaping colons in url template', function() {
|
||||
var R = $resource('http://localhost\\:8080/Path/:a/\\:stillPath/:b');
|
||||
|
||||
@@ -56,6 +56,29 @@ describe('ngView', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should instantiate controller for empty template', function() {
|
||||
var log = [], controllerScope,
|
||||
Ctrl = function($scope) {
|
||||
controllerScope = $scope;
|
||||
log.push('ctrl-init');
|
||||
};
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/some', {templateUrl: '/tpl.html', controller: Ctrl});
|
||||
});
|
||||
|
||||
inject(function($route, $rootScope, $templateCache, $location) {
|
||||
$templateCache.put('/tpl.html', [200, '', {}]);
|
||||
$location.path('/some');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(controllerScope.$parent).toBe($rootScope);
|
||||
expect(controllerScope).toBe($route.current.scope);
|
||||
expect(log).toEqual(['ctrl-init']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should instantiate controller with an alias', function() {
|
||||
var log = [], controllerScope,
|
||||
Ctrl = function($scope) {
|
||||
|
||||
Reference in New Issue
Block a user