Compare commits

...

8 Commits

Author SHA1 Message Date
Peter Bacon Darwin e201f9040f docs(CHANGELOG): update with 1.3.19 changes 2015-09-15 13:34:09 +01:00
Peter Bacon Darwin 40e9bcd1b4 chore(scripts/publish): get dist-tag from package.json
Closes #12722
2015-09-14 21:45:20 +01:00
Matias Niemelä f98e038418 feat(ngAnimate): introduce $animate.flush for unit testing 2015-09-14 13:08:57 -07:00
Lucas Galfaso ec98c94ccb fix($parse): throw error when accessing a restricted property indirectly
When accessing an instance thru a computed member and the property is an array,
then also check the string value of the array.

Closes #12833
2015-09-13 16:35:46 +01:00
Pawel Kozlowski f13055a0a5 fix($http): propagate status -1 for timed out requests
Fixes #4491
Closes #8756
2015-09-07 14:34:10 +01:00
Peter Bacon Darwin 623ce1ad2c fix($location): don't crash if navigating outside the app base
Previously, if you navigate outside of the Angular application, say be clicking
the back button, the $location service would try to handle the url change
and error due to the URL not being valid for the application.

This fixes that issue by ensuring that a reload happens when you navigate
to a URL that is not within the application.

Closes #11667
2015-09-07 14:33:17 +01:00
Peter Bacon Darwin 34cf141838 refactor($location): compute appBaseNoFile only once 2015-09-07 14:33:17 +01:00
Martin Staffa 274e93537e fix(ngModel): validate pattern against the viewValue
Since the HTML5 pattern validation constraint validates the input value,
we should also validate against the viewValue. While this worked in
core up to Angular 1.2, in 1.3, we changed not only validation,
but the way `input[date]` and `input[number]` are handled - they parse
their input values into `Date` and `Number` respectively, which cannot
be validated by a regex.

Fixes #12344

BREAKING CHANGE:

The `ngPattern` and `pattern` directives will validate the regex
against the `viewValue` of `ngModel`, i.e. the value of the model
before the $parsers are applied. Previously, the modelValue
(the result of the $parsers) was validated.

This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.

This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.

If you rely on the pattern being validated against the modelValue,
you must create your own validator directive that overwrites
the built-in pattern validator:

```
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
  return {
    restrict: 'A',
    require: '?ngModel',
    priority: 1,
    compile: function() {
      var regexp, patternExp;

      return {
        pre: function(scope, elm, attr, ctrl) {
          if (!ctrl) return;

          attr.$observe('pattern', function(regex) {
            /**
             * The built-in directive will call our overwritten validator
             * (see below). We just need to update the regex.
             * The preLink fn guaranetees our observer is called first.
             */
            if (isString(regex) && regex.length > 0) {
              regex = new RegExp('^' + regex + '$');
            }

            if (regex && !regex.test) {
              //The built-in validator will throw at this point
              return;
            }

            regexp = regex || undefined;
          });

        },
        post: function(scope, elm, attr, ctrl) {
          if (!ctrl) return;

          regexp, patternExp = attr.ngPattern || attr.pattern;

          //The postLink fn guarantees we overwrite the built-in pattern validator
          ctrl.$validators.pattern = function(value) {
            return ctrl.$isEmpty(value) ||
              isUndefined(regexp) ||
              regexp.test(value);
          };
        }
      };
    }
  };
});
```
2015-08-28 10:57:07 +02:00
20 changed files with 689 additions and 349 deletions
+103
View File
@@ -1,3 +1,106 @@
<a name="1.3.19"></a>
# 1.3.19 glutinous-shriek (2015-09-15)
## Bug Fixes
- **$http:** propagate status -1 for timed out requests
([f13055a0](https://github.com/angular/angular.js/commit/f13055a0a53a39b160448713a5617edee6042801),
[#4491](https://github.com/angular/angular.js/issues/4491), [#8756](https://github.com/angular/angular.js/issues/8756))
- **$location:** don't crash if navigating outside the app base
([623ce1ad](https://github.com/angular/angular.js/commit/623ce1ad2cf68024719c5cae5d682d00195df30c),
[#11667](https://github.com/angular/angular.js/issues/11667))
- **$parse:** throw error when accessing a restricted property indirectly
([ec98c94c](https://github.com/angular/angular.js/commit/ec98c94ccbfc97b655447956738d5f6ff98b2f33),
[#12833](https://github.com/angular/angular.js/issues/12833))
- **ngModel:** validate pattern against the viewValue
([274e9353](https://github.com/angular/angular.js/commit/274e93537ed4e95aefeacea48909eb334894f0ac),
[#12344](https://github.com/angular/angular.js/issues/12344))
## Features
- **ngAnimate:** introduce `$animate.flush` for unit testing
([f98e0384](https://github.com/angular/angular.js/commit/f98e038418f7367b2373adcf4887f64a8e8bdcb0))
## Possible Breaking Changes
- **ngModel:** due to [274e9353](https://github.com/angular/angular.js/commit/274e93537ed4e95aefeacea48909eb334894f0ac),
The `ngPattern` and `pattern` directives will validate the regex
against the `viewValue` of `ngModel`, i.e. the value of the model
before the $parsers are applied. Previously, the modelValue
(the result of the $parsers) was validated.
This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.
This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.
If you rely on the pattern being validated against the modelValue,
you must create your own validator directive that overwrites
the built-in pattern validator:
```
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
return {
restrict: 'A',
require: '?ngModel',
priority: 1,
compile: function() {
var regexp, patternExp;
return {
pre: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
attr.$observe('pattern', function(regex) {
/**
* The built-in directive will call our overwritten validator
* (see below). We just need to update the regex.
* The preLink fn guaranetees our observer is called first.
*/
if (isString(regex) && regex.length > 0) {
regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
//The built-in validator will throw at this point
return;
}
regexp = regex || undefined;
});
},
post: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
regexp, patternExp = attr.ngPattern || attr.pattern;
//The postLink fn guarantees we overwrite the built-in pattern validator
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) ||
isUndefined(regexp) ||
regexp.test(value);
};
}
};
}
};
});
```
<a name="1.3.18"></a>
# 1.3.18 collective-penmanship (2015-08-18)
+1
View File
@@ -1,6 +1,7 @@
{
"name": "angularjs",
"branchVersion": "1.3.*",
"distTag": "previous_1_3",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
+4 -13
View File
@@ -110,19 +110,10 @@ function publish {
# don't publish every build to npm
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
# publish 1.2.x releases with the appropriate tag
# this ensures that `npm install` by default will not grab `1.2.x` releases
npm publish --tag=old
else
# publish releases as "latest"
npm publish
fi
else
# publish prerelease builds with the beta tag
npm publish --tag=beta
fi
# get the npm dist-tag from a custom property (distTag) in package.json
DIST_TAG=$(readJsonProp "package.json" "distTag")
echo "-- Publishing to npm as $DIST_TAG"
npm publish --tag=$DIST_TAG
fi
cd $SCRIPT_DIR
+3 -2
View File
@@ -43,8 +43,9 @@ var patternDirective = function() {
ctrl.$validate();
});
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
ctrl.$validators.pattern = function(modelValue, viewValue) {
// HTML5 pattern constraint validates the input value, so we validate the viewValue
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
};
}
};
+2 -2
View File
@@ -1110,8 +1110,8 @@ function $HttpProvider() {
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers, statusText) {
// normalize internal statuses to 0
status = Math.max(status, 0);
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;
(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
+16 -8
View File
@@ -89,12 +89,12 @@ function serverBase(url) {
*
* @constructor
* @param {string} appBase application base URL
* @param {string} appBaseNoFile application base URL stripped of any filename
* @param {string} basePrefix url path prefix
*/
function LocationHtml5Url(appBase, basePrefix) {
function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
this.$$html5 = true;
basePrefix = basePrefix || '';
var appBaseNoFile = stripFile(appBase);
parseAbsoluteUrl(appBase, this);
@@ -168,10 +168,10 @@ function LocationHtml5Url(appBase, basePrefix) {
*
* @constructor
* @param {string} appBase application base URL
* @param {string} appBaseNoFile application base URL stripped of any filename
* @param {string} hashPrefix hashbang prefix
*/
function LocationHashbangUrl(appBase, hashPrefix) {
var appBaseNoFile = stripFile(appBase);
function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
parseAbsoluteUrl(appBase, this);
@@ -280,14 +280,13 @@ function LocationHashbangUrl(appBase, hashPrefix) {
*
* @constructor
* @param {string} appBase application base URL
* @param {string} appBaseNoFile application base URL stripped of any filename
* @param {string} hashPrefix hashbang prefix
*/
function LocationHashbangInHtml5Url(appBase, hashPrefix) {
function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
this.$$html5 = true;
LocationHashbangUrl.apply(this, arguments);
var appBaseNoFile = stripFile(appBase);
this.$$parseLinkUrl = function(url, relHref) {
if (relHref && relHref[0] === '#') {
// special case for links to hash fragments:
@@ -823,7 +822,9 @@ function $LocationProvider() {
appBase = stripHash(initialUrl);
LocationMode = LocationHashbangUrl;
}
$location = new LocationMode(appBase, '#' + hashPrefix);
var appBaseNoFile = stripFile(appBase);
$location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
$location.$$parseLinkUrl(initialUrl, initialUrl);
$location.$$state = $browser.state();
@@ -903,6 +904,13 @@ function $LocationProvider() {
// update $location when $browser url changes
$browser.onUrlChange(function(newUrl, newState) {
if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
// If we are navigating outside of the app then force a reload
$window.location.href = newUrl;
return;
}
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var oldState = $location.$$state;
+9
View File
@@ -38,6 +38,15 @@ var $parseMinErr = minErr('$parse');
function ensureSafeMemberName(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__") {
+48 -10
View File
@@ -782,8 +782,8 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
};
});
$provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser',
function($delegate, $$asyncCallback, $timeout, $browser) {
$provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser', '$rootScope', '$$rAF',
function($delegate, $$asyncCallback, $timeout, $browser, $rootScope, $$rAF) {
var animate = {
queue: [],
cancel: $delegate.cancel,
@@ -803,6 +803,43 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
fn();
});
reflowQueue = [];
},
flush: function() {
$rootScope.$digest();
var doNextRun, somethingFlushed = false;
do {
doNextRun = false;
if (reflowQueue.length) {
doNextRun = somethingFlushed = true;
this.triggerReflow();
}
if ($$rAF.queue.length) {
doNextRun = somethingFlushed = true;
$$rAF.flush();
}
if ($$asyncCallback.queue.length) {
doNextRun = somethingFlushed = true;
this.triggerCallbackEvents();
}
if (timeoutsRemaining()) {
var oldValue = timeoutsRemaining();
this.triggerCallbackPromise();
var newValue = timeoutsRemaining();
if (newValue < oldValue) {
doNextRun = somethingFlushed = true;
}
}
} while (doNextRun);
if (!somethingFlushed) {
throw new Error('No pending animations ready to be closed or flushed');
}
$rootScope.$digest();
function timeoutsRemaining() {
return $browser.deferredFns.length;
}
}
};
@@ -1752,8 +1789,7 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
}];
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
var queue = [];
var rafFn = function(fn) {
var queue, rafFn = function(fn) {
var index = queue.length;
queue.push(fn);
return function() {
@@ -1761,6 +1797,8 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
};
};
queue = rafFn.queue = [];
rafFn.supported = $delegate.supported;
rafFn.flush = function() {
@@ -1773,22 +1811,22 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
queue[i]();
}
queue = [];
queue.length = 0;
};
return rafFn;
}];
angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) {
var callbacks = [];
var addFn = function(fn) {
callbacks.push(fn);
var queue, addFn = function(fn) {
queue.push(fn);
};
queue = addFn.queue = [];
addFn.flush = function() {
angular.forEach(callbacks, function(fn) {
angular.forEach(queue, function(fn) {
fn();
});
callbacks = [];
queue.length = 0;
};
return addFn;
}];
+2 -1
View File
@@ -165,6 +165,7 @@
"spyOnlyCallsWithArgs": false,
"createMockStyleSheet": false,
"browserTrigger": false,
"jqLiteCacheSize": false
"jqLiteCacheSize": false,
"browserSupportsCssAnimations": false
}
}
+9
View File
@@ -56,3 +56,12 @@ function createMockStyleSheet(doc, wind) {
}
};
}
function browserSupportsCssAnimations() {
var nav = window.navigator.appVersion;
if (nav.indexOf('MSIE') >= 0) {
var version = parseInt(navigator.appVersion.match(/MSIE ([\d.]+)/)[1]);
return version >= 10; //only IE10+ support keyframes / transitions
}
return true;
}
+1 -1
View File
@@ -472,7 +472,7 @@ describe('ngClass animations', function() {
//is spaced-out then it is required so that the original digestion
//is kicked into gear
$rootScope.$digest();
$animate.triggerCallbacks();
$animate.flush();
expect(element.data('state')).toBe('crazy-enter');
expect(enterComplete).toBe(true);
+7 -7
View File
@@ -433,7 +433,7 @@ describe('ngInclude', function() {
expect(autoScrollSpy).not.toHaveBeenCalled();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
@@ -450,7 +450,7 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$apply(function() {
$rootScope.tpl = 'another.html';
@@ -459,7 +459,7 @@ describe('ngInclude', function() {
expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
@@ -468,7 +468,7 @@ describe('ngInclude', function() {
expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).toHaveBeenCalled();
expect(autoScrollSpy.callCount).toBe(3);
@@ -484,7 +484,7 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
@@ -500,7 +500,7 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
@@ -522,7 +522,7 @@ describe('ngInclude', function() {
$rootScope.$apply("tpl = 'template.html'");
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}
+1 -1
View File
@@ -1486,7 +1486,7 @@ describe('ngRepeat animations', function() {
$rootScope.$digest();
expect(element.text()).toBe('123'); // the original order should be preserved
$animate.triggerReflow();
$animate.flush();
$timeout.flush(1500); // 1s * 1.5 closing buffer
expect(element.text()).toBe('13');
+15
View File
@@ -204,6 +204,21 @@ describe('validators', function() {
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
});
it('should validate the viewValue and not the modelValue', function() {
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
var ctrl = inputElm.controller('ngModel');
ctrl.$parsers.push(function(value) {
return (value * 10) + '';
});
helper.changeInputValueTo('1234');
expect($rootScope.form.test.$error.pattern).not.toBe(true);
expect($rootScope.form.test.$modelValue).toBe('12340');
expect(inputElm).toBeValid();
});
});
+1 -1
View File
@@ -1657,7 +1657,7 @@ describe('$http', function() {
$http({method: 'GET', url: '/some', timeout: canceler.promise}).error(
function(data, status, headers, config) {
expect(data).toBeUndefined();
expect(status).toBe(0);
expect(status).toBe(-1);
expect(headers()).toEqual({});
expect(config.url).toBe('/some');
callback();
+49 -35
View File
@@ -46,7 +46,7 @@ describe('$location', function() {
it('should not include the drive name in path() on WIN', function() {
//See issue #4680 for details
var locationUrl = new LocationHashbangUrl('file:///base', '#!');
var locationUrl = new LocationHashbangUrl('file:///base', 'file:///', '#!');
locationUrl.$$parse('file:///base#!/foo?a=b&c#hash');
expect(locationUrl.path()).toBe('/foo');
@@ -54,7 +54,7 @@ describe('$location', function() {
it('should include the drive name if it was provided in the input url', function() {
var locationUrl = new LocationHashbangUrl('file:///base', '#!');
var locationUrl = new LocationHashbangUrl('file:///base', 'file:///', '#!');
locationUrl.$$parse('file:///base#!/C:/foo?a=b&c#hash');
expect(locationUrl.path()).toBe('/C:/foo');
@@ -64,7 +64,7 @@ describe('$location', function() {
describe('NewUrl', function() {
function createLocationHtml5Url() {
var locationUrl = new LocationHtml5Url('http://www.domain.com:9877/');
var locationUrl = new LocationHtml5Url('http://www.domain.com:9877/', 'http://www.domain.com:9877/');
locationUrl.$$parse('http://www.domain.com:9877/path/b?search=a&b=c&d#hash');
return locationUrl;
}
@@ -299,18 +299,18 @@ describe('$location', function() {
it('should parse new url', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/base');
expect(locationUrl.path()).toBe('/base');
locationUrl = new LocationHtml5Url('http://host.com/');
locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/base#');
expect(locationUrl.path()).toBe('/base');
});
it('should prefix path with forward-slash', function() {
var locationUrl = new LocationHtml5Url('http://server/');
var locationUrl = new LocationHtml5Url('http://server/', 'http://server/') ;
locationUrl.path('b');
expect(locationUrl.path()).toBe('/b');
@@ -319,7 +319,7 @@ describe('$location', function() {
it('should set path to forward-slash when empty', function() {
var locationUrl = new LocationHtml5Url('http://server/');
var locationUrl = new LocationHtml5Url('http://server/', 'http://server/') ;
locationUrl.$$parse('http://server/');
expect(locationUrl.path()).toBe('/');
expect(locationUrl.absUrl()).toBe('http://server/');
@@ -356,7 +356,7 @@ describe('$location', function() {
});
it('should prepend path with basePath', function() {
var locationUrl = new LocationHtml5Url('http://server/base/');
var locationUrl = new LocationHtml5Url('http://server/base/', 'http://server/base/') ;
locationUrl.$$parse('http://server/base/abc?a');
expect(locationUrl.path()).toBe('/abc');
expect(locationUrl.search()).toEqual({a: true});
@@ -367,7 +367,7 @@ describe('$location', function() {
it('should throw error when invalid server url given', function() {
var locationUrl = new LocationHtml5Url('http://server.org/base/abc', '/base');
var locationUrl = new LocationHtml5Url('http://server.org/base/abc', 'http://server.org/base/', '/base');
expect(function() {
locationUrl.$$parse('http://other.server.org/path#/path');
@@ -376,7 +376,7 @@ describe('$location', function() {
it('should throw error when invalid base url given', function() {
var locationUrl = new LocationHtml5Url('http://server.org/base/abc', '/base');
var locationUrl = new LocationHtml5Url('http://server.org/base/abc', 'http://server.org/base/', '/base');
expect(function() {
locationUrl.$$parse('http://server.org/path#/path');
@@ -444,7 +444,7 @@ describe('$location', function() {
it('should decode special characters', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/a%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23');
expect(locationUrl.path()).toBe('/a <>#');
expect(locationUrl.search()).toEqual({'i j': '<>#'});
@@ -452,7 +452,7 @@ describe('$location', function() {
});
it('should decode pluses as spaces in urls', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/?a+b=c+d');
expect(locationUrl.search()).toEqual({'a b':'c d'});
});
@@ -470,7 +470,7 @@ describe('$location', function() {
describe('HashbangUrl', function() {
function createHashbangUrl() {
var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base', '#!');
var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base', 'http://www.server.org:1234/', '#!');
locationUrl.$$parse('http://www.server.org:1234/base#!/path?a=b&c#hash');
return locationUrl;
}
@@ -499,7 +499,7 @@ describe('$location', function() {
it('should preserve query params in base', function() {
var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base?base=param', '#');
var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base?base=param', 'http://www.server.org:1234/', '#');
locationUrl.$$parse('http://www.server.org:1234/base?base=param#/path?a=b&c#hash');
expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base?base=param#/path?a=b&c#hash');
@@ -511,7 +511,7 @@ describe('$location', function() {
it('should prefix path with forward-slash', function() {
var locationUrl = new LocationHashbangUrl('http://host.com/base', '#');
var locationUrl = new LocationHashbangUrl('http://host.com/base', 'http://host.com/', '#');
locationUrl.$$parse('http://host.com/base#path');
expect(locationUrl.path()).toBe('/path');
expect(locationUrl.absUrl()).toBe('http://host.com/base#/path');
@@ -523,7 +523,7 @@ describe('$location', function() {
it('should set path to forward-slash when empty', function() {
var locationUrl = new LocationHashbangUrl('http://server/base', '#!');
var locationUrl = new LocationHashbangUrl('http://server/base', 'http://server/', '#!');
locationUrl.$$parse('http://server/base');
locationUrl.path('aaa');
@@ -592,7 +592,7 @@ describe('$location', function() {
it('should decode special characters', function() {
var locationUrl = new LocationHashbangUrl('http://host.com/a', '#');
var locationUrl = new LocationHashbangUrl('http://host.com/a', 'http://host.com/', '#');
locationUrl.$$parse('http://host.com/a#/%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23');
expect(locationUrl.path()).toBe('/ <>#');
expect(locationUrl.search()).toEqual({'i j': '<>#'});
@@ -601,35 +601,35 @@ describe('$location', function() {
it('should return decoded characters for search specified in URL', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/?q=1%2F2%203');
expect(locationUrl.search()).toEqual({'q': '1/2 3'});
});
it('should return decoded characters for search specified with setter', function() {
var locationUrl = new LocationHtml5Url('http://host.com/');
var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/');
locationUrl.$$parse('http://host.com/');
locationUrl.search('q', '1/2 3');
expect(locationUrl.search()).toEqual({'q': '1/2 3'});
});
it('should return an array for duplicate params', function() {
var locationUrl = new LocationHtml5Url('http://host.com');
var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com') ;
locationUrl.$$parse('http://host.com');
locationUrl.search('q', ['1/2 3','4/5 6']);
expect(locationUrl.search()).toEqual({'q': ['1/2 3','4/5 6']});
});
it('should encode an array correctly from search and add to url', function() {
var locationUrl = new LocationHtml5Url('http://host.com');
var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com') ;
locationUrl.$$parse('http://host.com');
locationUrl.search({'q': ['1/2 3','4/5 6']});
expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203&q=4%2F5%206');
});
it('should rewrite params when specifing a single param in search', function() {
var locationUrl = new LocationHtml5Url('http://host.com');
var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com') ;
locationUrl.$$parse('http://host.com');
locationUrl.search({'q': '1/2 3'});
expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203');
@@ -860,7 +860,6 @@ describe('$location', function() {
});
});
// location.href = '...' fires hashchange event synchronously, so it might happen inside $apply
it('should not $apply when browser url changed inside $apply', function() {
initService({html5Mode:false,hashPrefix: '!',supportHistory: true});
@@ -1150,6 +1149,19 @@ describe('$location', function() {
expect($browserUrl.mostRecentCall.args).toEqual(['http://new.com/a/b/bar', false, null]);
});
});
it('should force a page reload if navigating outside of the application base href', function() {
initService({html5Mode:true, supportHistory: true});
mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});
inject(function($window, $browser, $location) {
$window.location.href = 'http://new.com/a/outside.html';
spyOn($window.location, '$$setHref');
expect($window.location.$$setHref).not.toHaveBeenCalled();
$browser.$$checkUrlChange();
expect($window.location.$$setHref).toHaveBeenCalledWith('http://new.com/a/outside.html');
});
});
});
@@ -2353,8 +2365,8 @@ describe('$location', function() {
var locationUrl, locationIndexUrl;
beforeEach(function() {
locationUrl = new LocationHtml5Url('http://server/pre/', 'http://server/pre/path');
locationIndexUrl = new LocationHtml5Url('http://server/pre/index.html', 'http://server/pre/path');
locationUrl = new LocationHtml5Url('http://server/pre/', 'http://server/pre/', 'http://server/pre/path');
locationIndexUrl = new LocationHtml5Url('http://server/pre/index.html', 'http://server/pre/', 'http://server/pre/path');
});
it('should rewrite URL', function() {
@@ -2416,7 +2428,7 @@ describe('$location', function() {
it('should rewrite URL', function() {
/* jshint scripturl: true */
locationUrl = new LocationHashbangUrl('http://server/pre/', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/', 'http://server/pre/', '#');
expect(parseLinkAndReturn(locationUrl, 'http://other')).toEqual(undefined);
expect(parseLinkAndReturn(locationUrl, 'http://server/pre/')).toEqual('http://server/pre/');
@@ -2425,7 +2437,7 @@ describe('$location', function() {
});
it("should not set hash if one was not originally specified", function() {
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#');
locationUrl.$$parse('http://server/pre/index.html');
expect(locationUrl.url()).toBe('');
@@ -2433,7 +2445,7 @@ describe('$location', function() {
});
it("should parse hash if one was specified", function() {
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#');
locationUrl.$$parse('http://server/pre/index.html#/foo/bar');
expect(locationUrl.url()).toBe('/foo/bar');
@@ -2442,7 +2454,7 @@ describe('$location', function() {
it("should prefix hash url with / if one was originally missing", function() {
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#');
locationUrl.$$parse('http://server/pre/index.html#not-starting-with-slash');
expect(locationUrl.url()).toBe('/not-starting-with-slash');
@@ -2452,7 +2464,7 @@ describe('$location', function() {
it('should not strip stuff from path just because it looks like Windows drive when it\'s not',
function() {
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#');
locationUrl.$$parse('http://server/pre/index.html#http%3A%2F%2Fexample.com%2F');
expect(locationUrl.url()).toBe('/http://example.com/');
@@ -2464,7 +2476,7 @@ describe('$location', function() {
});
it('should allow navigating outside the original base URL', function() {
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', '#');
locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#');
locationUrl.$$parse('http://server/next/index.html');
expect(locationUrl.url()).toBe('');
@@ -2478,8 +2490,8 @@ describe('$location', function() {
var locationUrl, locationIndexUrl;
beforeEach(function() {
locationUrl = new LocationHashbangInHtml5Url('http://server/pre/', '#!');
locationIndexUrl = new LocationHashbangInHtml5Url('http://server/pre/index.html', '#!');
locationUrl = new LocationHashbangInHtml5Url('http://server/pre/', 'http://server/pre/', '#!');
locationIndexUrl = new LocationHashbangInHtml5Url('http://server/pre/index.html', 'http://server/pre/', '#!');
});
it('should rewrite URL', function() {
@@ -2540,8 +2552,10 @@ describe('$location', function() {
win.addEventListener = angular.noop;
win.removeEventListener = angular.noop;
win.location = {
get href() { return parser.href; },
set href(val) { parser.href = val; },
get href() { return this.$$getHref(); },
$$getHref: function() { return parser.href; },
set href(val) { this.$$setHref(val); },
$$setHref: function(val) { parser.href = val; },
get hash() { return parser.hash; },
// The parser correctly strips on a single preceding hash character if necessary
// before joining the fragment onto the href by a new hash character
+14
View File
@@ -1190,6 +1190,20 @@ describe('parser', function() {
scope.$eval('{}["__proto__"].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('{}[["__proto__"]]');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('{}[["__proto__"]].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('0[["__proto__"]]');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('0[["__proto__"]].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
scope.a = "__pro";
scope.b = "to__";
expect(function() {
File diff suppressed because it is too large Load Diff
+179
View File
@@ -1828,6 +1828,185 @@ describe('ngMockE2E', function() {
}));
});
});
describe('ngAnimateMock', function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
var ss, element, trackedAnimations;
beforeEach(module(function($animateProvider) {
trackedAnimations = [];
$animateProvider.register('.animate', function($timeout) {
return {
leave: logFn('leave'),
addClass: logFn('addClass')
};
function logFn(method) {
return function(element) {
trackedAnimations.push(getDoneCallback(arguments));
// this will never finish an animation so we'll issue a call
// to timeout so that the mock driver won't throw an exception
$timeout(angular.noop, 0, false);
};
}
function getDoneCallback(args) {
for (var i = args.length; i > 0; i--) {
if (angular.isFunction(args[i])) return args[i];
}
}
});
return function($animate, $rootElement, $document, $rootScope, $window) {
if (ss) {
ss.destroy();
}
ss = createMockStyleSheet($document, $window);
element = angular.element('<div class="animate"></div>');
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
$animate.enabled(true);
$rootScope.$digest();
};
}));
describe('$animate.queue', function() {
it('should maintain a queue of the executed animations', inject(function($animate) {
element.removeClass('animate'); // we don't care to test any actual animations
var options = {};
$animate.addClass(element, 'on', options);
var first = $animate.queue[0];
expect(first.element).toBe(element);
expect(first.event).toBe('addClass');
expect(first.options).toBe(options);
$animate.removeClass(element, 'off', options);
var second = $animate.queue[1];
expect(second.element).toBe(element);
expect(second.event).toBe('removeClass');
expect(second.options).toBe(options);
$animate.leave(element, options);
var third = $animate.queue[2];
expect(third.element).toBe(element);
expect(third.event).toBe('leave');
expect(third.options).toBe(options);
}));
});
describe('$animate.flush()', function() {
it('should throw an error if there is nothing to animate', inject(function($animate) {
expect(function() {
$animate.flush();
}).toThrow('No pending animations ready to be closed or flushed');
}));
it('should trigger the animation to start',
inject(function($animate) {
expect(trackedAnimations.length).toBe(0);
$animate.leave(element);
$animate.flush();
expect(trackedAnimations.length).toBe(1);
}));
it('should trigger the animation to end once run and called',
inject(function($animate) {
$animate.leave(element);
$animate.flush();
expect(element.parent().length).toBe(1);
trackedAnimations[0]();
$animate.flush();
expect(element.parent().length).toBe(0);
}));
it('should trigger the animation promise callback to fire once run and closed',
inject(function($animate) {
var doneSpy = jasmine.createSpy();
$animate.leave(element).then(doneSpy);
$animate.flush();
trackedAnimations[0]();
expect(doneSpy).not.toHaveBeenCalled();
$animate.flush();
expect(doneSpy).toHaveBeenCalled();
}));
it('should trigger a series of CSS animations to trigger and start once run',
inject(function($animate, $rootScope) {
if (!browserSupportsCssAnimations()) return;
ss.addRule('.leave-me.ng-leave', 'transition:1s linear all;');
var i, elm, elms = [];
for (i = 0; i < 5; i++) {
elm = angular.element('<div class="leave-me"></div>');
element.append(elm);
elms.push(elm);
$animate.leave(elm);
}
$rootScope.$digest();
for (i = 0; i < 5; i++) {
elm = elms[i];
expect(elm.hasClass('ng-leave')).toBe(true);
expect(elm.hasClass('ng-leave-active')).toBe(false);
}
$animate.flush();
for (i = 0; i < 5; i++) {
elm = elms[i];
expect(elm.hasClass('ng-leave')).toBe(true);
expect(elm.hasClass('ng-leave-active')).toBe(true);
}
}));
it('should trigger parent and child animations to run within the same flush',
inject(function($animate, $rootScope) {
var child = angular.element('<div class="animate child"></div>');
element.append(child);
expect(trackedAnimations.length).toBe(0);
$animate.addClass(element, 'go');
$animate.addClass(child, 'start');
$animate.flush();
expect(trackedAnimations.length).toBe(2);
}));
it('should trigger animation callbacks when called',
inject(function($animate, $rootScope) {
var spy = jasmine.createSpy();
element.on('$animate:before', spy);
element.on('$animate:close', spy);
$animate.addClass(element, 'on');
expect(spy).not.toHaveBeenCalled();
$animate.flush();
expect(spy.callCount).toBe(1);
trackedAnimations[0]();
$animate.flush();
expect(spy.callCount).toBe(2);
}));
});
});
});
describe('make sure that we can create an injector outside of tests', function() {
+7 -7
View File
@@ -712,7 +712,7 @@ describe('ngView animations', function() {
$location.path('/foo');
$rootScope.$digest();
$animate.triggerCallbacks();
$animate.flush();
$location.path('/');
$rootScope.$digest();
@@ -776,7 +776,7 @@ describe('ngView animations', function() {
expect($animate.queue.shift().event).toBe('addClass');
expect($animate.queue.shift().event).toBe('removeClass');
$animate.triggerReflow();
$animate.flush();
expect(item.hasClass('classy')).toBe(false);
expect(item.hasClass('boring')).toBe(true);
@@ -914,7 +914,7 @@ describe('ngView animations', function() {
$location.path('/foo');
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
@@ -928,7 +928,7 @@ describe('ngView animations', function() {
$location.path('/foo');
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
@@ -941,7 +941,7 @@ describe('ngView animations', function() {
$location.path('/foo');
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
@@ -955,7 +955,7 @@ describe('ngView animations', function() {
$location.path('/foo');
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
@@ -972,7 +972,7 @@ describe('ngView animations', function() {
expect(autoScrollSpy).not.toHaveBeenCalled();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
expect($animate.enter).toHaveBeenCalledOnce();
expect(autoScrollSpy).toHaveBeenCalledOnce();