Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b586bfdfab | |||
| 1d69015e3d | |||
| f13c33bf10 | |||
| ba62e975f1 | |||
| b89d941cdf | |||
| 07fa87a8a8 | |||
| 0af70eb99e | |||
| cb713e6045 | |||
| 89ca859734 | |||
| bc6fb7cc94 | |||
| 0c80df21b6 | |||
| 3141dbf179 | |||
| aad502bad8 | |||
| dca8972367 | |||
| 284de57435 | |||
| f780ccfa1c | |||
| 55f99e0710 | |||
| 1a99ca9c08 | |||
| 37500fca83 | |||
| 4fe4fc5abf | |||
| 74e1cc683b | |||
| a4faa5cde7 | |||
| 32cb40b86d | |||
| d1cd677433 | |||
| 43c735a816 | |||
| ab2e83c8c8 | |||
| e5f454c8af | |||
| 67c11b9a39 | |||
| 5a306b7ba3 | |||
| 8ce61bf178 | |||
| 9d452bc845 | |||
| 192fecc790 | |||
| 32be6369e4 | |||
| 2a45cea0ba | |||
| ea653e4cdd | |||
| 03777445e8 | |||
| 8b25ea129a | |||
| d71f16e745 | |||
| ed59370d80 | |||
| d8e5acfe27 | |||
| af59f4e69a | |||
| 24aee81634 | |||
| f81d56e66c |
@@ -1,3 +1,49 @@
|
||||
<a name="1.2.18"></a>
|
||||
# 1.2.18 ear-extendability (2014-06-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- ensure transclude works at root of templateUrl
|
||||
([fd420c40](https://github.com/angular/angular.js/commit/fd420c40613d02b3a3f7b14d00a98664518c28f0),
|
||||
[#7183](https://github.com/angular/angular.js/issues/7183), [#7772](https://github.com/angular/angular.js/issues/7772))
|
||||
- bound transclusion to correct scope
|
||||
([1382d4e8](https://github.com/angular/angular.js/commit/1382d4e88ec486b7749e45e6ccc864b3ec388cfe))
|
||||
- don't pass transcludes to non-transclude templateUrl directives
|
||||
([b9ddef2a](https://github.com/angular/angular.js/commit/b9ddef2a495b44cb5fe678b8753de0b7a369244d))
|
||||
- don't pass transclude to template of non-transclude directive
|
||||
([eafba9e2](https://github.com/angular/angular.js/commit/eafba9e2e5ddc668c534e930d83031d2e8dc32b9))
|
||||
- fix nested isolated transclude directives
|
||||
([bb931097](https://github.com/angular/angular.js/commit/bb9310974b6765c2b87e74ee7b8485a6e9c24740),
|
||||
[#1809](https://github.com/angular/angular.js/issues/1809), [#7499](https://github.com/angular/angular.js/issues/7499))
|
||||
- pass transcludeFn down to nested transclude directives
|
||||
([8df5f325](https://github.com/angular/angular.js/commit/8df5f3259aa776f28bf3d869fb1c03e10a897c84),
|
||||
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
|
||||
- **$injector:** report circularity in circular dependency error message
|
||||
([14e797c1](https://github.com/angular/angular.js/commit/14e797c1a10eabd15bf8e845b62213398bcc0f58),
|
||||
[#7500](https://github.com/angular/angular.js/issues/7500))
|
||||
- **ngResource:** don't convert literal values into Resource objects when isArray is true
|
||||
([f0904cf1](https://github.com/angular/angular.js/commit/f0904cf12e4f01daa2d4fcbb20c762050125ca55),
|
||||
[#6314](https://github.com/angular/angular.js/issues/6314), [#7741](https://github.com/angular/angular.js/issues/7741))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** move ng-binding class stamping for interpolation into compile phase
|
||||
([81b7e5ab](https://github.com/angular/angular.js/commit/81b7e5ab0ee3fea410b16b09144359ceb99f5191))
|
||||
- **$http:** move xsrf cookie check to after cache check in $http
|
||||
([8b86d363](https://github.com/angular/angular.js/commit/8b86d363aa252c3264201b54b57c3e34f9632d45),
|
||||
[#7717](https://github.com/angular/angular.js/issues/7717))
|
||||
- **isArray:** use native Array.isArray
|
||||
([6c14fb1e](https://github.com/angular/angular.js/commit/6c14fb1eb61dc0a0552fbcb2ca3ace11c9a2f6a5))
|
||||
- **jqLite:** cache collection length for all methods that work on a single element
|
||||
([6d418ef5](https://github.com/angular/angular.js/commit/6d418ef5e3a775577996caf0709f79f447f77025))
|
||||
- **ngBind:** set the ng-binding class during compilation instead of linking
|
||||
([1b189027](https://github.com/angular/angular.js/commit/1b1890274e5a75553ddf9915bb23da48800275f9))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.17"></a>
|
||||
# 1.2.17 - quantum disentanglement (2014-06-06)
|
||||
|
||||
|
||||
+4
-1
@@ -220,8 +220,11 @@ module.exports = function(grunt) {
|
||||
|
||||
"ddescribe-iit": {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/ngScenario/DescribeSpec.js'
|
||||
'!test/ngScenario/DescribeSpec.js',
|
||||
'!src/ng/directive/booleanAttrs.js', // legitimate xit here
|
||||
'!src/ngScenario/**/*.js'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecff
|
||||
@fullName Referencing 'call', 'apply' and 'bind' Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
|
||||
|
||||
Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
|
||||
since access is a known way to modify the behaviour of existing functions.
|
||||
|
||||
To resolve this error, avoid using these methods in expressions.
|
||||
|
||||
Example expression that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.sendInfo.call({}, true)}}</div>
|
||||
```
|
||||
@@ -1,18 +1,27 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecfld
|
||||
@fullName Referencing 'constructor' Field in Expression
|
||||
@fullName Referencing Disallowed Field in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access an objects constructor field.
|
||||
Occurs when an expression attempts to access one of the following fields:
|
||||
|
||||
AngularJS bans constructor access from within expressions since constructor
|
||||
access is a known way to execute arbitrary Javascript code.
|
||||
* __proto__
|
||||
* __defineGetter__
|
||||
* __defineSetter__
|
||||
* __lookupGetter__
|
||||
* __lookupSetter__
|
||||
|
||||
To resolve this error, avoid constructor access. As a last resort, alias
|
||||
the constructor and access it through the alias instead.
|
||||
AngularJS bans access to these fields from within expressions since
|
||||
access is a known way to mess with native objects or
|
||||
to execute arbitrary Javascript code.
|
||||
|
||||
Example expression that would result in this error:
|
||||
To resolve this error, avoid using these fields in expressions. As a last resort,
|
||||
alias their value and access them through the alias instead.
|
||||
|
||||
Example expressions that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.constructor.name}}</div>
|
||||
```
|
||||
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>
|
||||
|
||||
<div>{{user.__defineGetter__('name', noop)}}</div>
|
||||
```
|
||||
@@ -60,7 +60,7 @@ changes to $location are reflected into the browser address bar.
|
||||
|
||||
<tr>
|
||||
<td class="head">aware of docroot/context from which the application is loaded</td>
|
||||
<td>no - window.location.path returns "/docroot/actual/path"</td>
|
||||
<td>no - window.location.pathname returns "/docroot/actual/path"</td>
|
||||
<td>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ moved to the compile function for performance reasons.
|
||||
To understand, let's look at a real-world example with `ngRepeat`:
|
||||
|
||||
```html
|
||||
Hello {{user}}, you have these actions:
|
||||
Hello {{user.name}}, you have these actions:
|
||||
<ul>
|
||||
<li ng-repeat="action in user.actions">
|
||||
{{action.description}}
|
||||
@@ -236,7 +236,7 @@ Hello {{user}}, you have these actions:
|
||||
|
||||
When the above example is compiled, the compiler visits every node and looks for directives.
|
||||
|
||||
`{{user}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
`{{user.name}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
and `ng-repeat` matches the {@link ng.directive:ngRepeat `ngRepeat` directive}.
|
||||
|
||||
But {@link ng.directive:ngRepeat ngRepeat} has a dilemma.
|
||||
|
||||
@@ -22,7 +22,7 @@ The impedance mismatch between dynamic applications and static documents is ofte
|
||||
in charge and it calls into the library when it sees fit. E.g., `jQuery`.
|
||||
* **frameworks** - a particular implementation of a web application, where your code fills in
|
||||
the details. The framework is in charge and it calls into your code when it needs something
|
||||
app specific. E.g., `knockout`, `ember`, etc.
|
||||
app specific. E.g., `durandal`, `ember`, etc.
|
||||
|
||||
|
||||
Angular takes another approach. It attempts to minimize the impedance mismatch between document
|
||||
|
||||
@@ -49,7 +49,7 @@ Out of the four options in the list above, only the last one is testable. Let's
|
||||
### Using the `new` operator
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
|
||||
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
|
||||
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
|
||||
```js
|
||||
|
||||
@@ -10,7 +10,7 @@ becoming an Angular expert.
|
||||
1. Read the {@link guide/concepts conceptual overview}.<br/>Understand Angular's vocabulary and how all the Angular
|
||||
components work together.
|
||||
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building an application complete with tests
|
||||
on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
|
||||
on top of a node.js web server. Covers every major AngularJS feature and shows you how to set up your development
|
||||
environment.
|
||||
1. Download or clone the [Seed App project template](https://github.com/angular/angular-seed).<br/>Gives you a
|
||||
starter app with a directory layout, test harness, and scripts to begin building your application.
|
||||
|
||||
@@ -166,7 +166,7 @@ __`test/e2e/scenarios.js`:__
|
||||
"MOTOROLA XOOM\u2122"
|
||||
]);
|
||||
|
||||
element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click();
|
||||
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
|
||||
|
||||
expect(getNames()).toEqual([
|
||||
"MOTOROLA XOOM\u2122",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"assertNotHasOwnProperty": false,
|
||||
"getter": false,
|
||||
"getBlockElements": false,
|
||||
"VALIDITY_STATE_PROPERTY": false,
|
||||
|
||||
/* AngularPublic.js */
|
||||
"version": false,
|
||||
|
||||
+7
-2
@@ -13,6 +13,7 @@
|
||||
-angularModule,
|
||||
-nodeName_,
|
||||
-uid,
|
||||
-VALIDITY_STATE_PROPERTY,
|
||||
|
||||
-lowercase,
|
||||
-uppercase,
|
||||
@@ -102,6 +103,10 @@
|
||||
* <div doc-module-components="ng"></div>
|
||||
*/
|
||||
|
||||
// The name of a form control's ValidityState property.
|
||||
// This is used so that it's possible for internal tests to create mock ValidityStates.
|
||||
var VALIDITY_STATE_PROPERTY = 'validity';
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.lowercase
|
||||
@@ -1274,7 +1279,7 @@ function angularInit(element, bootstrap) {
|
||||
*
|
||||
* Angular will detect if it has been loaded into the browser more than once and only allow the
|
||||
* first loaded script to be bootstrapped and will report a warning to the browser console for
|
||||
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
|
||||
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
|
||||
* multiple instances of Angular try to work on the DOM.
|
||||
*
|
||||
* <example name="multi-bootstrap" module="multi-bootstrap">
|
||||
@@ -1404,7 +1409,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) {
|
||||
}
|
||||
|
||||
assertArg(isFunction(arg), name, 'not a function, got ' +
|
||||
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
|
||||
(arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -13,16 +13,16 @@
|
||||
* @returns {string} hash string such that the same input will have the same hash string.
|
||||
* The resulting string key is in 'type:hashKey' format.
|
||||
*/
|
||||
function hashKey(obj) {
|
||||
function hashKey(obj, nextUidFn) {
|
||||
var objType = typeof obj,
|
||||
key;
|
||||
|
||||
if (objType == 'object' && obj !== null) {
|
||||
if (objType == 'function' || (objType == 'object' && obj !== null)) {
|
||||
if (typeof (key = obj.$$hashKey) == 'function') {
|
||||
// must invoke on object to keep the right this
|
||||
key = obj.$$hashKey();
|
||||
} else if (key === undefined) {
|
||||
key = obj.$$hashKey = nextUid();
|
||||
key = obj.$$hashKey = (nextUidFn || nextUid)();
|
||||
}
|
||||
} else {
|
||||
key = obj;
|
||||
@@ -34,7 +34,13 @@ function hashKey(obj) {
|
||||
/**
|
||||
* HashMap which can use objects as keys
|
||||
*/
|
||||
function HashMap(array){
|
||||
function HashMap(array, isolatedUid) {
|
||||
if (isolatedUid) {
|
||||
var uid = 0;
|
||||
this.nextUid = function() {
|
||||
return ++uid;
|
||||
};
|
||||
}
|
||||
forEach(array, this.put, this);
|
||||
}
|
||||
HashMap.prototype = {
|
||||
@@ -44,7 +50,7 @@ HashMap.prototype = {
|
||||
* @param value value to store can be any type
|
||||
*/
|
||||
put: function(key, value) {
|
||||
this[hashKey(key)] = value;
|
||||
this[hashKey(key, this.nextUid)] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -52,7 +58,7 @@ HashMap.prototype = {
|
||||
* @returns {Object} the value for the key
|
||||
*/
|
||||
get: function(key) {
|
||||
return this[hashKey(key)];
|
||||
return this[hashKey(key, this.nextUid)];
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -60,7 +66,7 @@ HashMap.prototype = {
|
||||
* @param key
|
||||
*/
|
||||
remove: function(key) {
|
||||
var value = this[key = hashKey(key)];
|
||||
var value = this[key = hashKey(key, this.nextUid)];
|
||||
delete this[key];
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ function annotate(fn) {
|
||||
argDecl,
|
||||
last;
|
||||
|
||||
if (typeof fn == 'function') {
|
||||
if (typeof fn === 'function') {
|
||||
if (!($inject = fn.$inject)) {
|
||||
$inject = [];
|
||||
if (fn.length) {
|
||||
@@ -285,7 +285,7 @@ function annotate(fn) {
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @ngdoc service
|
||||
* @name $provide
|
||||
*
|
||||
* @description
|
||||
@@ -591,7 +591,7 @@ function createInjector(modulesToLoad) {
|
||||
var INSTANTIATING = {},
|
||||
providerSuffix = 'Provider',
|
||||
path = [],
|
||||
loadedModules = new HashMap(),
|
||||
loadedModules = new HashMap([], true),
|
||||
providerCache = {
|
||||
$provide: {
|
||||
provider: supportObject(provider),
|
||||
@@ -762,8 +762,7 @@ function createInjector(modulesToLoad) {
|
||||
: getService(key)
|
||||
);
|
||||
}
|
||||
if (!fn.$inject) {
|
||||
// this means that we must be an array.
|
||||
if (isArray(fn)) {
|
||||
fn = fn[length];
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -98,8 +98,9 @@
|
||||
* @returns {Object} jQuery object.
|
||||
*/
|
||||
|
||||
JQLite.expando = 'ng339';
|
||||
|
||||
var jqCache = JQLite.cache = {},
|
||||
jqName = JQLite.expando = 'ng' + new Date().getTime(),
|
||||
jqId = 1,
|
||||
addEventListenerFn = (window.document.addEventListener
|
||||
? function(element, type, fn) {element.addEventListener(type, fn, false);}
|
||||
@@ -309,7 +310,7 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
}
|
||||
|
||||
function jqLiteRemoveData(element, name) {
|
||||
var expandoId = element[jqName],
|
||||
var expandoId = element.ng339,
|
||||
expandoStore = jqCache[expandoId];
|
||||
|
||||
if (expandoStore) {
|
||||
@@ -323,17 +324,17 @@ function jqLiteRemoveData(element, name) {
|
||||
jqLiteOff(element);
|
||||
}
|
||||
delete jqCache[expandoId];
|
||||
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
|
||||
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
|
||||
}
|
||||
}
|
||||
|
||||
function jqLiteExpandoStore(element, key, value) {
|
||||
var expandoId = element[jqName],
|
||||
var expandoId = element.ng339,
|
||||
expandoStore = jqCache[expandoId || -1];
|
||||
|
||||
if (isDefined(value)) {
|
||||
if (!expandoStore) {
|
||||
element[jqName] = expandoId = jqNextId();
|
||||
element.ng339 = expandoId = jqNextId();
|
||||
expandoStore = jqCache[expandoId] = {};
|
||||
}
|
||||
expandoStore[key] = value;
|
||||
|
||||
+9
-5
@@ -1010,7 +1010,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
|
||||
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
|
||||
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
||||
var attrStartName = false;
|
||||
var attrEndName = false;
|
||||
@@ -1018,9 +1018,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attr = nAttrs[j];
|
||||
if (!msie || msie >= 8 || attr.specified) {
|
||||
name = attr.name;
|
||||
value = trim(attr.value);
|
||||
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
}
|
||||
|
||||
@@ -1033,9 +1035,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
attrs[nName] = value = trim(attr.value);
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
|
||||
@@ -74,7 +74,7 @@ function $ControllerProvider() {
|
||||
instance = $injector.instantiate(expression, locals);
|
||||
|
||||
if (identifier) {
|
||||
if (!(locals && typeof locals.$scope == 'object')) {
|
||||
if (!(locals && typeof locals.$scope === 'object')) {
|
||||
throw minErr('$controller')('noscp',
|
||||
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
|
||||
constructor || expression.name, identifier);
|
||||
|
||||
+30
-13
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
|
||||
|
||||
var inputType = {
|
||||
@@ -435,15 +435,29 @@ function validate(ctrl, validatorName, validity, value){
|
||||
return validity ? value : undefined;
|
||||
}
|
||||
|
||||
function testFlags(validity, flags) {
|
||||
var i, flag;
|
||||
if (flags) {
|
||||
for (i=0; i<flags.length; ++i) {
|
||||
flag = flags[i];
|
||||
if (validity[flag]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addNativeHtml5Validators(ctrl, validatorName, element) {
|
||||
var validity = element.prop('validity');
|
||||
// Pass validity so that behaviour can be mocked easier.
|
||||
function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
|
||||
if (isObject(validity)) {
|
||||
ctrl.$$hasNativeValidators = true;
|
||||
var validator = function(value) {
|
||||
// Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
|
||||
// perform the required validation)
|
||||
if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError ||
|
||||
validity.typeMismatch) && !validity.valueMissing) {
|
||||
if (!ctrl.$error[validatorName] &&
|
||||
!testFlags(validity, ignoreFlags) &&
|
||||
testFlags(validity, badFlags)) {
|
||||
ctrl.$setValidity(validatorName, false);
|
||||
return;
|
||||
}
|
||||
@@ -454,8 +468,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
|
||||
}
|
||||
|
||||
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var validity = element.prop('validity');
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY);
|
||||
var placeholder = element[0].placeholder, noevent = {};
|
||||
ctrl.$$validityState = validity;
|
||||
|
||||
// In composition mode, users are still inputing intermediate text buffer,
|
||||
// hold the listener until composition is done.
|
||||
@@ -493,11 +508,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
value = trim(value);
|
||||
}
|
||||
|
||||
if (ctrl.$viewValue !== value ||
|
||||
// If the value is still empty/falsy, and there is no `required` error, run validators
|
||||
// again. This enables HTML5 constraint validation errors to affect Angular validation
|
||||
// even when the first character entered causes an error.
|
||||
(validity && value === '' && !validity.valueMissing)) {
|
||||
// If a control is suffering from bad input, browsers discard its value, so it may be
|
||||
// necessary to revalidate even if the control's value is the same empty value twice in
|
||||
// a row.
|
||||
var revalidate = validity && ctrl.$$hasNativeValidators;
|
||||
if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
|
||||
if (scope.$$phase) {
|
||||
ctrl.$setViewValue(value);
|
||||
} else {
|
||||
@@ -603,6 +618,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
}
|
||||
|
||||
var numberBadFlags = ['badInput'];
|
||||
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
@@ -617,7 +634,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
});
|
||||
|
||||
addNativeHtml5Validators(ctrl, 'number', element);
|
||||
addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
|
||||
|
||||
ctrl.$formatters.push(function(value) {
|
||||
return ctrl.$isEmpty(value) ? '' : '' + value;
|
||||
@@ -1099,7 +1116,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* This method should be called by validators - i.e. the parser or formatter functions.
|
||||
*
|
||||
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
|
||||
* to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
|
||||
* to `$error[validationErrorKey]=!isValid` so that it is available for data-binding.
|
||||
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
|
||||
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
|
||||
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
* When one person, perhaps John, views the document, "John is viewing" will be shown.
|
||||
* When three people view the document, no explicit number rule is found, so
|
||||
* an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
|
||||
* In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
|
||||
* In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
|
||||
* is shown.
|
||||
*
|
||||
* Note that when you specify offsets, you must provide explicit number rules for
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<file name="protractor.js" type="protractor">
|
||||
var colorSpan = element(by.css('span'));
|
||||
|
||||
iit('should check ng-style', function() {
|
||||
it('should check ng-style', function() {
|
||||
expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
|
||||
element(by.css('input[value=\'set color\']')).click();
|
||||
expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
|
||||
|
||||
@@ -553,7 +553,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// rather then the element.
|
||||
(element = optionTemplate.clone())
|
||||
.val(option.id)
|
||||
.attr('selected', option.selected)
|
||||
.prop('selected', option.selected)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ function filterFilter() {
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
if (typeof expression[path] == 'undefined') return;
|
||||
if (typeof expression[path] === 'undefined') return;
|
||||
predicates.push(function(value) {
|
||||
return search(path == '$' ? value : (value && value[path]), expression[path]);
|
||||
});
|
||||
|
||||
@@ -131,6 +131,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
numStr = '0';
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
hasExponent = true;
|
||||
@@ -145,8 +146,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
|
||||
}
|
||||
|
||||
var pow = Math.pow(10, fractionSize + 1);
|
||||
number = Math.floor(number * pow + 5) / pow;
|
||||
// safely round numbers in JS without hitting imprecisions of floating-point arithmetics
|
||||
// inspired by:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
|
||||
@@ -81,7 +81,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
// Safari respectively.
|
||||
if (xhr && xhr.readyState == 4) {
|
||||
var responseHeaders = null,
|
||||
response = null;
|
||||
response = null,
|
||||
statusText = '';
|
||||
|
||||
if(status !== ABORTED) {
|
||||
responseHeaders = xhr.getAllResponseHeaders();
|
||||
@@ -91,11 +92,17 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
response = ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
}
|
||||
|
||||
// Accessing statusText on an aborted xhr object will
|
||||
// throw an 'c00c023f error' in IE9 and lower, don't touch it.
|
||||
if (!(status === ABORTED && msie < 10)) {
|
||||
statusText = xhr.statusText;
|
||||
}
|
||||
|
||||
completeRequest(callback,
|
||||
status || xhr.status,
|
||||
response,
|
||||
responseHeaders,
|
||||
xhr.statusText || '');
|
||||
statusText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ function $IntervalProvider() {
|
||||
interval.cancel = function(promise) {
|
||||
if (promise && promise.$$intervalId in intervals) {
|
||||
intervals[promise.$$intervalId].reject('canceled');
|
||||
clearInterval(promise.$$intervalId);
|
||||
$window.clearInterval(promise.$$intervalId);
|
||||
delete intervals[promise.$$intervalId];
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-2
@@ -551,7 +551,7 @@ function $LocationProvider(){
|
||||
html5Mode = false;
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @ngdoc method
|
||||
* @name $locationProvider#hashPrefix
|
||||
* @description
|
||||
* @param {string=} prefix Prefix for hash part (containing path and search)
|
||||
@@ -567,7 +567,7 @@ function $LocationProvider(){
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @ngdoc method
|
||||
* @name $locationProvider#html5Mode
|
||||
* @description
|
||||
* @param {boolean=} mode Use HTML5 strategy if available.
|
||||
|
||||
+36
-15
@@ -12,14 +12,7 @@ var promiseWarning;
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
//
|
||||
// {}.toString.constructor(alert("evil JS code"))
|
||||
//
|
||||
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
|
||||
// disallow any "dotted" access to any member named "constructor".
|
||||
//
|
||||
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
|
||||
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
|
||||
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
|
||||
// {}.toString.constructor('alert("evil JS code")')
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
@@ -27,17 +20,19 @@ var promiseWarning;
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
// A developer could foil the name check by aliasing the Function constructor under a different
|
||||
// name on the scope.
|
||||
//
|
||||
// In general, it is not possible to access a Window object from an angular expression unless a
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
if (name === "constructor") {
|
||||
if (name === "__defineGetter__" || name === "__defineSetter__"
|
||||
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -59,11 +54,34 @@ function ensureSafeObject(obj, fullExpression) {
|
||||
throw $parseMinErr('isecdom',
|
||||
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// block Object so that we can't get hold of dangerous Object.* methods
|
||||
obj === Object) {
|
||||
throw $parseMinErr('isecobj',
|
||||
'Referencing Object in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var CALL = Function.prototype.call;
|
||||
var APPLY = Function.prototype.apply;
|
||||
var BIND = Function.prototype.bind;
|
||||
|
||||
function ensureSafeFunction(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) {
|
||||
throw $parseMinErr('isecff',
|
||||
'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var OPERATORS = {
|
||||
/* jshint bitwise : false */
|
||||
'null':function(){return null;},
|
||||
@@ -698,6 +716,7 @@ Parser.prototype = {
|
||||
i = indexFn(self, locals),
|
||||
v, p;
|
||||
|
||||
ensureSafeMemberName(i, parser.text);
|
||||
if (!o) return undefined;
|
||||
v = ensureSafeObject(o[i], parser.text);
|
||||
if (v && v.then && parser.options.unwrapPromises) {
|
||||
@@ -740,7 +759,7 @@ Parser.prototype = {
|
||||
var fnPtr = fn(scope, locals, context) || noop;
|
||||
|
||||
ensureSafeObject(context, parser.text);
|
||||
ensureSafeObject(fnPtr, parser.text);
|
||||
ensureSafeFunction(fnPtr, parser.text);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
var v = fnPtr.apply
|
||||
@@ -849,6 +868,8 @@ function setter(obj, path, setValue, fullExp, options) {
|
||||
}
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
obj[key] = setValue;
|
||||
return setValue;
|
||||
}
|
||||
|
||||
+6
-10
@@ -23,17 +23,13 @@
|
||||
* var deferred = $q.defer();
|
||||
*
|
||||
* setTimeout(function() {
|
||||
* // since this fn executes async in a future turn of the event loop, we need to wrap
|
||||
* // our code into an $apply call so that the model changes are properly observed.
|
||||
* scope.$apply(function() {
|
||||
* deferred.notify('About to greet ' + name + '.');
|
||||
* deferred.notify('About to greet ' + name + '.');
|
||||
*
|
||||
* if (okToGreet(name)) {
|
||||
* deferred.resolve('Hello, ' + name + '!');
|
||||
* } else {
|
||||
* deferred.reject('Greeting ' + name + ' is not allowed.');
|
||||
* }
|
||||
* });
|
||||
* if (okToGreet(name)) {
|
||||
* deferred.resolve('Hello, ' + name + '!');
|
||||
* } else {
|
||||
* deferred.reject('Greeting ' + name + ' is not allowed.');
|
||||
* }
|
||||
* }, 1000);
|
||||
*
|
||||
* return deferred.promise;
|
||||
|
||||
+1
-1
@@ -639,7 +639,7 @@ function $RootScopeProvider(){
|
||||
if ((value = watch.get(current)) !== (last = watch.last) &&
|
||||
!(watch.eq
|
||||
? equals(value, last)
|
||||
: (typeof value == 'number' && typeof last == 'number'
|
||||
: (typeof value === 'number' && typeof last === 'number'
|
||||
&& isNaN(value) && isNaN(last)))) {
|
||||
dirty = true;
|
||||
lastDirtyWatch = watch;
|
||||
|
||||
+1
-1
@@ -764,7 +764,7 @@ function $SceProvider() {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $sce#parse
|
||||
* @name $sce#parseAs
|
||||
*
|
||||
* @description
|
||||
* Converts Angular {@link guide/expression expression} into a function. This is like {@link
|
||||
|
||||
Vendored
+8
-2
@@ -455,7 +455,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
iteration = 0,
|
||||
skipApply = (angular.isDefined(invokeApply) && !invokeApply);
|
||||
|
||||
count = (angular.isDefined(count)) ? count : 0,
|
||||
count = (angular.isDefined(count)) ? count : 0;
|
||||
promise.then(null, null, fn);
|
||||
|
||||
promise.$$intervalId = nextRepeatId;
|
||||
@@ -1719,7 +1719,7 @@ angular.mock.$RootElementProvider = function() {
|
||||
*
|
||||
* # ngMock
|
||||
*
|
||||
* The `ngMock` module providers support to inject and mock Angular services into unit tests.
|
||||
* The `ngMock` module provides support to inject and mock Angular services into unit tests.
|
||||
* In addition, ngMock also extends various core ng services such that they can be
|
||||
* inspected and controlled in a synchronous manner within test code.
|
||||
*
|
||||
@@ -1958,6 +1958,12 @@ if(window.jasmine || window.mocha) {
|
||||
(window.afterEach || window.teardown)(function() {
|
||||
var injector = currentSpec.$injector;
|
||||
|
||||
angular.forEach(currentSpec.$modules, function(module) {
|
||||
if (module && module.$$hashKey) {
|
||||
module.$$hashKey = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
currentSpec.$injector = null;
|
||||
currentSpec.$modules = null;
|
||||
currentSpec = null;
|
||||
|
||||
@@ -294,7 +294,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
function push(value) {
|
||||
if (value === undefined) {
|
||||
value = '';
|
||||
} else if (typeof value != 'string') {
|
||||
} else if (typeof value !== 'string') {
|
||||
value = angular.toJson(value);
|
||||
}
|
||||
result.push('' + value);
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"assertNotHasOwnProperty": false,
|
||||
"getter": false,
|
||||
"getBlockElements": false,
|
||||
"VALIDITY_STATE_PROPERTY": true,
|
||||
|
||||
/* filters.js */
|
||||
"getFirstThursdayOfYear": false,
|
||||
|
||||
@@ -22,6 +22,36 @@ describe('api', function() {
|
||||
expect(map.get('b')).toBe(1);
|
||||
expect(map.get('c')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should maintain hashKey for object keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should maintain hashKey for function keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = function() {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should share hashKey between HashMap by default', function() {
|
||||
var map1 = new HashMap(), map2 = new HashMap();
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).not.toEqual(key2.$$hashKey);
|
||||
});
|
||||
|
||||
it('should maintain hashKey per HashMap if flag is passed', function() {
|
||||
var map1 = new HashMap([], true), map2 = new HashMap([], true);
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).toEqual(key2.$$hashKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -293,6 +293,29 @@ describe('injector', function() {
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should load different instances of dependent functions', function() {
|
||||
function generateValueModule(name, value) {
|
||||
return function ($provide) {
|
||||
$provide.value(name, value);
|
||||
};
|
||||
}
|
||||
var injector = createInjector([generateValueModule('name1', 'value1'),
|
||||
generateValueModule('name2', 'value2')]);
|
||||
expect(injector.get('name2')).toBe('value2');
|
||||
});
|
||||
|
||||
it('should load same instance of dependent function only once', function() {
|
||||
var count = 0;
|
||||
function valueModule($provide) {
|
||||
count++;
|
||||
$provide.value('name', 'value');
|
||||
}
|
||||
|
||||
var injector = createInjector([valueModule, valueModule]);
|
||||
expect(injector.get('name')).toBe('value');
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it('should execute runBlocks after injector creation', function() {
|
||||
var log = '';
|
||||
angular.module('a', [], function(){ log += 'a'; }).run(function() { log += 'A'; });
|
||||
|
||||
@@ -248,13 +248,13 @@ function isCssVisible(node) {
|
||||
|
||||
function assertHidden(node) {
|
||||
if (isCssVisible(node)) {
|
||||
throw new Error('Node should be hidden but was visible: ' + angular.module.ngMock.dump(node));
|
||||
throw new Error('Node should be hidden but was visible: ' + angular.mock.dump(node));
|
||||
}
|
||||
}
|
||||
|
||||
function assertVisible(node) {
|
||||
if (!isCssVisible(node)) {
|
||||
throw new Error('Node should be visible but was hidden: ' + angular.module.ngMock.dump(node));
|
||||
throw new Error('Node should be visible but was hidden: ' + angular.mock.dump(node));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4901,6 +4901,83 @@ describe('$compile', function() {
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
element = $compile('<span test="123" ng-attr-test="{{name}}"></span>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
element = $compile('<span ng-attr-test="{{name}}" test="123"></span>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('test')).toBe('Misko');
|
||||
}));
|
||||
|
||||
|
||||
describe('in directive', function() {
|
||||
beforeEach(module(function() {
|
||||
directive('syncTest', function(log) {
|
||||
return {
|
||||
link: {
|
||||
pre: function(s, e, attr) { log(attr.test); },
|
||||
post: function(s, e, attr) { log(attr.test); }
|
||||
}
|
||||
};
|
||||
});
|
||||
directive('asyncTest', function(log) {
|
||||
return {
|
||||
templateUrl: 'async.html',
|
||||
link: {
|
||||
pre: function(s, e, attr) { log(attr.test); },
|
||||
post: function(s, e, attr) { log(attr.test); }
|
||||
}
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($templateCache) {
|
||||
$templateCache.put('async.html', '<h1>Test</h1>');
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in synchronous directive link functions when after overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in synchronous directive link functions when before overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide post-digest value in asynchronous directive link functions when after overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
|
||||
it('should provide post-digest value in asynchronous directive link functions when before overridden attribute',
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
$rootScope.test = "TEST";
|
||||
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
|
||||
expect(element.attr('test')).toBe('123');
|
||||
$rootScope.$digest();
|
||||
expect(log.toArray()).toEqual(['TEST', 'TEST']);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work with different prefixes', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
|
||||
@@ -410,15 +410,33 @@ describe('ngModel', function() {
|
||||
|
||||
|
||||
describe('input', function() {
|
||||
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo;
|
||||
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo, currentSpec;
|
||||
|
||||
function compileInput(inputHtml) {
|
||||
function compileInput(inputHtml, mockValidity) {
|
||||
inputElm = jqLite(inputHtml);
|
||||
if (isObject(mockValidity)) {
|
||||
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
|
||||
inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
|
||||
currentSpec.after(function() {
|
||||
VALIDITY_STATE_PROPERTY = 'validity';
|
||||
});
|
||||
}
|
||||
formElm = jqLite('<form name="form"></form>');
|
||||
formElm.append(inputElm);
|
||||
$compile(formElm)(scope);
|
||||
}
|
||||
|
||||
var attrs;
|
||||
beforeEach(function() { currentSpec = this; });
|
||||
afterEach(function() { currentSpec = null; });
|
||||
beforeEach(module(function($compileProvider) {
|
||||
$compileProvider.directive('attrCapture', function() {
|
||||
return function(scope, element, $attrs) {
|
||||
attrs = $attrs;
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector, _$sniffer_, _$browser_) {
|
||||
$sniffer = _$sniffer_;
|
||||
$browser = _$browser_;
|
||||
@@ -844,6 +862,33 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should invalidate number if suffering from bad input', function() {
|
||||
compileInput('<input type="number" ng-model="age" />', {
|
||||
valid: false,
|
||||
badInput: true
|
||||
});
|
||||
|
||||
changeInputValueTo('10a');
|
||||
expect(scope.age).toBeUndefined();
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate number if transition from bad input to empty string', function() {
|
||||
var validity = {
|
||||
valid: false,
|
||||
badInput: true
|
||||
};
|
||||
compileInput('<input type="number" ng-model="age" />', validity);
|
||||
changeInputValueTo('10a');
|
||||
validity.badInput = false;
|
||||
validity.valid = true;
|
||||
changeInputValueTo('');
|
||||
expect(scope.age).toBeNull();
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
describe('min', function() {
|
||||
|
||||
it('should validate', function() {
|
||||
|
||||
@@ -145,6 +145,10 @@ describe('filters', function() {
|
||||
expect(number(.99, 2)).toEqual("0.99");
|
||||
expect(number(.999, 3)).toEqual("0.999");
|
||||
expect(number(.9999, 3)).toEqual("1.000");
|
||||
expect(number(1.9, 2)).toEqual("1.90");
|
||||
expect(number(1.99, 2)).toEqual("1.99");
|
||||
expect(number(1.999, 3)).toEqual("1.999");
|
||||
expect(number(1.9999, 3)).toEqual("2.000");
|
||||
expect(number(1234.567, 0)).toEqual("1,235");
|
||||
expect(number(1234.567, 1)).toEqual("1,234.6");
|
||||
expect(number(1234.567, 2)).toEqual("1,234.57");
|
||||
@@ -152,6 +156,7 @@ describe('filters', function() {
|
||||
expect(number(1.255, 1)).toEqual("1.3");
|
||||
expect(number(1.255, 2)).toEqual("1.26");
|
||||
expect(number(1.255, 3)).toEqual("1.255");
|
||||
expect(number(0, 8)).toEqual("0.00000000");
|
||||
});
|
||||
|
||||
it('should filter exponentially large numbers', function() {
|
||||
|
||||
@@ -98,6 +98,25 @@ describe('$httpBackend', function() {
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should not touch xhr.statusText when request is aborted on IE9 or lower', function() {
|
||||
callback.andCallFake(function(status, response, headers, statusText) {
|
||||
expect(statusText).toBe((!msie || msie >= 10) ? 'OK' : '');
|
||||
});
|
||||
|
||||
$backend('GET', '/url', null, callback, {}, 2000);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
spyOn(xhr, 'abort');
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(xhr.abort).toHaveBeenCalledOnce();
|
||||
|
||||
xhr.status = 0;
|
||||
xhr.readyState = 4;
|
||||
xhr.statusText = 'OK';
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should call completion function with empty string if not present', function() {
|
||||
callback.andCallFake(function(status, response, headers, statusText) {
|
||||
expect(statusText).toBe('');
|
||||
|
||||
@@ -267,4 +267,25 @@ describe('$interval', function() {
|
||||
expect($interval.cancel()).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('$window delegation', function() {
|
||||
it('should use $window.setInterval instead of the global function', inject(function ($interval, $window) {
|
||||
var setIntervalSpy = spyOn($window, 'setInterval');
|
||||
|
||||
$interval(noop, 1000);
|
||||
expect(setIntervalSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should use $window.clearInterval instead of the global function', inject(function ($interval, $window) {
|
||||
var clearIntervalSpy = spyOn($window, 'clearInterval');
|
||||
|
||||
$interval(noop, 1000, 1);
|
||||
$window.flush(1000);
|
||||
expect(clearIntervalSpy).toHaveBeenCalled();
|
||||
|
||||
clearIntervalSpy.reset();
|
||||
$interval.cancel($interval(noop, 1000));
|
||||
expect(clearIntervalSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
+225
-92
@@ -637,90 +637,52 @@ describe('parser', function() {
|
||||
expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
|
||||
});
|
||||
|
||||
|
||||
describe('sandboxing', function() {
|
||||
describe('Function constructor', function() {
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor("alert(1)")');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor("alert(1)")');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('[].toString.constructor.foo');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: [].toString.constructor.foo');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]');
|
||||
expect(function() {
|
||||
scope.$eval('{}["toString"]["constructor"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}["toString"]["constructor"]');
|
||||
|
||||
scope.a = [];
|
||||
expect(function() {
|
||||
scope.$eval('a.toString.constructor', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor');
|
||||
expect(function() {
|
||||
scope.$eval('a.toString["constructor"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString["constructor"]');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in setter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor.a = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor.a = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["constructor"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["constructor"] = 1');
|
||||
|
||||
|
||||
scope.key1 = "const";
|
||||
scope.key2 = "ructor";
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString[key1 + key2].foo = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString[key1 + key2].foo = 1');
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString[key1 + key2].foo = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["a"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["a"] = 1');
|
||||
|
||||
scope.a = [];
|
||||
expect(function() {
|
||||
scope.$eval('a.toString.constructor = 1', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor = 1');
|
||||
});
|
||||
|
||||
@@ -730,21 +692,84 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]');
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function prototype functions', function () {
|
||||
it('should NOT allow invocation to Function.call', function() {
|
||||
scope.fn = Function.prototype.call;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.call()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.call()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('fn()')
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: fn()');
|
||||
});
|
||||
|
||||
it('should NOT allow invocation to Function.apply', function() {
|
||||
scope.apply = Function.prototype.apply;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.apply()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: apply()');
|
||||
});
|
||||
|
||||
|
||||
// IE8 doesn't have Function.prototype.bind
|
||||
if (!msie || msie > 8) {
|
||||
it('should NOT allow invocation to Function.bind', function () {
|
||||
scope.bind = Function.prototype.bind;
|
||||
|
||||
expect(function () {
|
||||
scope.$eval('$eval.bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.bind()');
|
||||
|
||||
expect(function () {
|
||||
scope.$eval('bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: bind()');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Object constructor', function() {
|
||||
|
||||
it('should NOT allow access to Object constructor that has been aliased', function() {
|
||||
scope.foo = { "bar": Object };
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo.bar.keys(foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.bar.keys(foo)');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]["keys"](foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window and $element/node', function() {
|
||||
it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
|
||||
@@ -753,15 +778,16 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('wrap["w"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["w"]');
|
||||
expect(function() {
|
||||
scope.$eval('wrap["d"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["d"]');
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
|
||||
scope.getWin = valueFn($window);
|
||||
scope.getDoc = valueFn($document);
|
||||
@@ -769,12 +795,12 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('getWin()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: getWin()');
|
||||
expect(function() {
|
||||
scope.$eval('getDoc()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: getDoc()');
|
||||
}));
|
||||
|
||||
@@ -783,12 +809,12 @@ describe('parser', function() {
|
||||
expect(function() {
|
||||
scope.$eval('a.b.win.alert(1)', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.win.alert(1)');
|
||||
expect(function() {
|
||||
scope.$eval('a.b.doc.on("click")', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.doc.on("click")');
|
||||
}));
|
||||
|
||||
@@ -814,39 +840,145 @@ describe('parser', function() {
|
||||
expect(function() { scope.$eval('array'); }).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overriding constructor', function() {
|
||||
it('should evaluate grouped expressions', function() {
|
||||
scope.foo = function foo() {
|
||||
return "foo";
|
||||
};
|
||||
// When not overridden, access should be restricted both by the dot operator and by the
|
||||
// index operator.
|
||||
expect(function() {
|
||||
scope.$eval('foo.constructor()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.constructor()');
|
||||
expect(function() {
|
||||
scope.$eval('foo["constructor"]()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["constructor"]()');
|
||||
describe('Disallowed fields', function() {
|
||||
it('should NOT allow access or invocation of __defineGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
// User defined value assigned to constructor.
|
||||
scope.foo.constructor = function constructor() {
|
||||
return "custom constructor";
|
||||
};
|
||||
// Dot operator should still block it.
|
||||
expect(function() {
|
||||
scope.$eval('foo.constructor()', scope)
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.constructor()');
|
||||
// However, the index operator should allow it.
|
||||
expect(scope.$eval('foo["constructor"]()', scope)).toBe('custom constructor');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__define";
|
||||
scope.b = "Getter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __defineSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__define";
|
||||
scope.b = "Setter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__lookup";
|
||||
scope.b = "Getter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__lookup";
|
||||
scope.b = "Setter__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access to __proto__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__proto__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(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');
|
||||
|
||||
scope.a = "__pro";
|
||||
scope.b = "to__";
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
});
|
||||
|
||||
it('should prevent the exploit', function() {
|
||||
expect(function() {
|
||||
scope.$eval('' +
|
||||
' "".sub.call.call(' +
|
||||
'({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
|
||||
'null,' +
|
||||
'"alert(1)"' +
|
||||
')()' +
|
||||
'')
|
||||
}).toThrow();
|
||||
})
|
||||
});
|
||||
|
||||
it('should call the function from the received instance and not from a new one', function() {
|
||||
@@ -1005,6 +1137,7 @@ describe('parser', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('constant', function() {
|
||||
it('should mark scalar value expressions as constant', inject(function($parse) {
|
||||
expect($parse('12.3').constant).toBe(true);
|
||||
|
||||
Vendored
+17
@@ -805,6 +805,23 @@ describe('ngMock', function() {
|
||||
expect(example).toEqual('win');
|
||||
});
|
||||
});
|
||||
|
||||
describe('module cleanup', function() {
|
||||
function testFn() {
|
||||
|
||||
}
|
||||
|
||||
it('should add hashKey to module function', function() {
|
||||
module(testFn);
|
||||
inject(function () {
|
||||
expect(testFn.$$hashKey).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanup hashKey after previous test', function() {
|
||||
expect(testFn.$$hashKey).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in DSL', function() {
|
||||
|
||||
Reference in New Issue
Block a user