Compare commits

...

13 Commits

Author SHA1 Message Date
Martin Staffa 8302981a08 docs(CHANGELOG.md): add changes for 1.6.8 2017-12-18 15:17:56 +01:00
Martin Staffa d2a7b5162f docs(*): add browserTrigger docs; update references to scenario runner 2017-12-18 15:06:21 +01:00
Jason Bedard 2ecd85b989 test($rootScope): test recursive event broadcast and emit 2017-12-18 15:06:20 +01:00
Peter Bacon Darwin 2bdf712687 fix($location): always decode special chars in $location.url(value)
The original fix for #16312 included changing how `$location.url(value)`
decoded the special characters passed to it as a setter.
This broke a number of use cases (mostly involving the ui-router).

Further analysis appears to show that we can solve #16312, to prevent
urls being rewritten with decoded values, without modifying the
behaviour of `$location.url`.

This commit reverts changes to `$location.url(value)` so that encoded
chars will once again be decoded and passed to `$location.path(value)`.
In particular it will convert encoded forward slashes, which changes how
the path is updated, since e.g. `a/b/%2Fc%2Fd` will become `a/b/c/d`.
While this is arguably not "correct", it appears that there are too many
use cases relying upon this behaviour.
2017-12-11 19:41:38 +00:00
John Hampton 0de02973c1 docs(ng-model-options): remove extra quotes in example
Remove unnecessary quotes around attribute directive name in the docs example.  This syntax is incorrect.

Closes #16362
2017-12-11 10:57:46 +02:00
Martin Staffa 55516da2df fix(ngModelController): allow $overrideModelOptions to set updateOn
Also adds more docs about "default" events and how to override
ngModelController options.

Closes #16351
Closes #16364
2017-12-08 11:11:42 +01:00
Michał Gołębiowski-Owczarek e8adbf8d5a chore(*): bump Yarn in Jenkins init-node script
Without it Jenkins builds are broken.

Closes #16365
2017-12-07 20:28:02 +01:00
Michał Gołębiowski-Owczarek c169c60535 build(*): update Node from 6 to 8, update Yarn
Angular (2+) switched to Node 8 and so should we.

Closes #16360
Ref angular/angular#20807
Ref angular/angular#20832
2017-12-07 19:27:40 +01:00
Jason Bedard 0b6ec6b3ab refactor($rootScope): consistently use noop as the default $watch listener
Closes #16343
2017-12-07 11:14:29 +01:00
Martin Staffa d2511cfac0 chore(travis): fix deployment condition to include tagged commits
Tagged commits are not considered to belong to any branch.

Closes #16346
2017-12-07 11:13:09 +01:00
jugglinmike 155efa421b docs(ngNonBindable): document effect on the element's directives
The phrase "contents of the current DOM element" may be interpreted either as
inclusive of the DOM element's attributes or as exclusive of the attributes.
This situation concerns markup such as:

    <div ng-non-bindable ng-controller="MyController"></div>

In practice, AngularJS does not compile or bind attribute values for elements
which specify the `ng-non-bindable` directive. Extend the documentation to
definitely describe this behavior.

Closes #16338
2017-12-07 11:13:09 +01:00
Francesco Pipita f33d95cfcf feat($parse): add a hidden interface to retrieve an expression's AST
This PR adds a new private method to the `$parse` service, `$$getAst`,
which takes an Angular expression as its only argument and returns
the computed AST. This feature is not meant to be part of the public
API and might be subject to changes, so use it with caution.

Closes #16253

Closes #16260
2017-11-30 16:33:19 +02:00
Peter Bacon Darwin 57b626a673 fix($location): decode non-component special chars in Hashbang URLS
Fixes https://github.com/angular/angular.js/pull/16316#issuecomment-347527097
2017-11-30 13:23:16 +00:00
22 changed files with 314 additions and 55 deletions
+1 -1
View File
@@ -1 +1 @@
6
8
+4 -4
View File
@@ -1,7 +1,7 @@
language: node_js
sudo: false
node_js:
- '6'
- '8'
cache:
yarn: true
@@ -28,7 +28,7 @@ env:
- secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks=
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.27.5
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
- export PATH="$HOME/.yarn/bin:$PATH"
before_script:
@@ -54,10 +54,10 @@ notifications:
jobs:
include:
- stage: deploy
# Don't deploy from PRs and only from our default branches.
# Don't deploy from PRs. Only deploy from our default branches, or if commit is tagged.
# This is a Travis-specific boolean language: https://docs.travis-ci.com/user/conditional-builds-stages-jobs#Specifying-conditions
# The deployment logic for pushed branches is further defined in scripts\travis\build.sh
if: type != pull_request and branch =~ ^(v1\.\d+\.x|master)$
if: type != pull_request and (branch =~ ^(v1\.\d+\.x|master)$ or tag IS present)
env:
- JOB=deploy
before_script: skip
+22
View File
@@ -1,3 +1,25 @@
<a name="1.6.8"></a>
# 1.6.8 beneficial-tincture (2017-12-18)
## Bug Fixes
- **$location:**
- always decode special chars in `$location.url(value)`
([2bdf71](https://github.com/angular/angular.js/commit/2bdf7126878c87474bb7588ce093d0a3c57b0026))
- decode non-component special chars in Hashbang URLS
([57b626](https://github.com/angular/angular.js/commit/57b626a673b7530399d3377dfe770165bec35f8a))
- **ngModelController:** allow $overrideModelOptions to set updateOn
([55516d](https://github.com/angular/angular.js/commit/55516da2dfc7c5798dce24e9fa930c5ac90c900c),
[#16351](https://github.com/angular/angular.js/issues/16351),
[#16364](https://github.com/angular/angular.js/issues/16364))
## New Features
- **$parse:** add a hidden interface to retrieve an expression's AST
([f33d95](https://github.com/angular/angular.js/commit/f33d95cfcff6fd0270f92a142df8794cca2013ad),
[#16253](https://github.com/angular/angular.js/issues/16253),
[#16260](https://github.com/angular/angular.js/issues/16260))
<a name="1.6.7"></a>
# 1.6.7 imperial-backstroke (2017-11-24)
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="alert alert-danger">
**Note:** In the past, end-to-end testing could be done with a deprecated tool called
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
is now in maintenance mode.
is now in maintenance mode, and will be removed in version 1.7.0.
</div>
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
+2 -2
View File
@@ -9,8 +9,8 @@
"url": "https://github.com/angular/angular.js.git"
},
"engines": {
"node": "^6.9.1",
"yarn": ">=0.21.3",
"node": "^8.9.1",
"yarn": ">=1.3.2",
"grunt": "^1.2.0"
},
"scripts": {
+1 -1
View File
@@ -8,7 +8,7 @@ nvm install
# clean out and install yarn
rm -rf ~/.yarn
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.21.3
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
export PATH="$HOME/.yarn/bin:$PATH"
# Ensure that we have the local dependencies installed
-1
View File
@@ -67,7 +67,6 @@ function Browser(window, document, $log, $sniffer) {
/**
* @private
* Note: this method is used only by scenario runner
* TODO(vojta): prefix this method with $$ ?
* @param {function()} callback Function that will be called when no outstanding request
*/
+30 -5
View File
@@ -270,6 +270,9 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
this.$name = $interpolate($attr.name || '', false)($scope);
this.$$parentForm = nullFormCtrl;
this.$options = defaultModelOptions;
this.$$updateEvents = '';
// Attach the correct context to the event handler function for updateOn
this.$$updateEventHandler = this.$$updateEventHandler.bind(this);
this.$$parsedNgModel = $parse($attr.ngModel);
this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
@@ -875,11 +878,22 @@ NgModelController.prototype = {
* See {@link ngModelOptions} for information about what options can be specified
* and how model option inheritance works.
*
* <div class="alert alert-warning">
* **Note:** this function only affects the options set on the `ngModelController`,
* and not the options on the {@link ngModelOptions} directive from which they might have been
* obtained initially.
* </div>
*
* <div class="alert alert-danger">
* **Note:** it is not possible to override the `getterSetter` option.
* </div>
*
* @param {Object} options a hash of settings to override the previous options
*
*/
$overrideModelOptions: function(options) {
this.$options = this.$options.createChild(options);
this.$$setUpdateOnEvents();
},
/**
@@ -1027,6 +1041,21 @@ NgModelController.prototype = {
this.$modelValue = this.$$rawModelValue = modelValue;
this.$$parserValid = undefined;
this.$processModelValue();
},
$$setUpdateOnEvents: function() {
if (this.$$updateEvents) {
this.$$element.off(this.$$updateEvents, this.$$updateEventHandler);
}
this.$$updateEvents = this.$options.getOption('updateOn');
if (this.$$updateEvents) {
this.$$element.on(this.$$updateEvents, this.$$updateEventHandler);
}
},
$$updateEventHandler: function(ev) {
this.$$debounceViewValueCommit(ev && ev.type);
}
};
@@ -1318,11 +1347,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
},
post: function ngModelPostLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
if (modelCtrl.$options.getOption('updateOn')) {
element.on(modelCtrl.$options.getOption('updateOn'), function(ev) {
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
});
}
modelCtrl.$$setUpdateOnEvents();
function setTouched() {
modelCtrl.$setTouched();
+29 -3
View File
@@ -102,7 +102,7 @@ defaultModelOptions = new ModelOptions({
*
* The `ngModelOptions` settings are found by evaluating the value of the attribute directive as
* an Angular expression. This expression should evaluate to an object, whose properties contain
* the settings. For example: `<div "ng-model-options"="{ debounce: 100 }"`.
* the settings. For example: `<div ng-model-options="{ debounce: 100 }"`.
*
* ## Inheriting Options
*
@@ -177,6 +177,8 @@ defaultModelOptions = new ModelOptions({
* `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
* to have access to the updated model.
*
* ### Overriding immediate updates
*
* The following example shows how to override immediate updates. Changes on the inputs within the
* form will update the model only when the control loses focus (blur event). If `escape` key is
* pressed while the input field is focused, the value is reset to the value in the current model.
@@ -236,6 +238,8 @@ defaultModelOptions = new ModelOptions({
* </file>
* </example>
*
* ### Debouncing updates
*
* The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
* If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
*
@@ -260,6 +264,7 @@ defaultModelOptions = new ModelOptions({
* </file>
* </example>
*
*
* ## Model updates and validation
*
* The default behaviour in `ngModel` is that the model value is set to `undefined` when the
@@ -307,20 +312,41 @@ defaultModelOptions = new ModelOptions({
* You can specify the timezone that date/time input directives expect by providing its name in the
* `timezone` property.
*
*
* ## Programmatically changing options
*
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
* watched for changes. However, it is possible to override the options on a single
* {@link ngModel.NgModelController} instance with
* {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
*
*
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
* and its descendents. Valid keys are:
* - `updateOn`: string specifying which event should the input be bound to. You can set several
* events using an space delimited list. There is a special event called `default` that
* matches the default events belonging to the control.
* matches the default events belonging to the control. These are the events that are bound to
* the control, and when fired, update the `$viewValue` via `$setViewValue`.
*
* `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event,
* since different control types use different default events.
*
* See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates
* Triggering and debouncing model updates}.
*
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
* custom value for each event. For example:
* ```
* ng-model-options="{
* updateOn: 'default blur',
* updateOn: 'default blur click',
* debounce: { 'default': 500, 'blur': 0 }
* }"
* ```
*
* "default" also applies to all events that are listed in `updateOn` but are not
* listed in `debounce`, i.e. "click" would also be debounced by 500 milliseconds.
*
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
* not validate correctly instead of the default behavior of setting the model to undefined.
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
+5 -4
View File
@@ -8,10 +8,11 @@
* @element ANY
*
* @description
* The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
* DOM element. This is useful if the element contains what appears to be Angular directives and
* bindings but which should be ignored by Angular. This could be the case if you have a site that
* displays snippets of code, for instance.
* The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current
* DOM element, including directives on the element itself that have a lower priority than
* `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives
* and bindings but which should be ignored by AngularJS. This could be the case if you have a site
* that displays snippets of code, for instance.
*
* @example
* In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
+1 -3
View File
@@ -333,9 +333,7 @@ function $InterpolateProvider() {
var lastValue;
return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
var currValue = compute(values);
if (isFunction(listener)) {
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
}
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
lastValue = currValue;
});
}
+1 -1
View File
@@ -422,7 +422,7 @@ var locationPrototype = {
}
var match = PATH_MATCH.exec(url);
if (match[1] || url === '') this.path(decodeURI(match[1]));
if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
if (match[2] || match[1] || url === '') this.search(match[3] || '');
this.hash(match[5] || '');
+28 -10
View File
@@ -1644,11 +1644,26 @@ Parser.prototype = {
constructor: Parser,
parse: function(text) {
var ast = this.ast.ast(text);
var fn = this.astCompiler.compile(ast);
fn.literal = isLiteral(ast);
fn.constant = isConstant(ast);
var ast = this.getAst(text);
var fn = this.astCompiler.compile(ast.ast);
fn.literal = isLiteral(ast.ast);
fn.constant = isConstant(ast.ast);
fn.oneTime = ast.oneTime;
return fn;
},
getAst: function(exp) {
var oneTime = false;
exp = exp.trim();
if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
oneTime = true;
exp = exp.substring(2);
}
return {
ast: this.ast.ast(exp),
oneTime: oneTime
};
}
};
@@ -1771,10 +1786,11 @@ function $ParseProvider() {
isIdentifierStart: isFunction(identStart) && identStart,
isIdentifierContinue: isFunction(identContinue) && identContinue
};
$parse.$$getAst = $$getAst;
return $parse;
function $parse(exp, interceptorFn) {
var parsedExpression, oneTime, cacheKey;
var parsedExpression, cacheKey;
switch (typeof exp) {
case 'string':
@@ -1784,16 +1800,12 @@ function $ParseProvider() {
parsedExpression = cache[cacheKey];
if (!parsedExpression) {
if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
oneTime = true;
exp = exp.substring(2);
}
var lexer = new Lexer($parseOptions);
var parser = new Parser(lexer, $filter, $parseOptions);
parsedExpression = parser.parse(exp);
if (parsedExpression.constant) {
parsedExpression.$$watchDelegate = constantWatchDelegate;
} else if (oneTime) {
} else if (parsedExpression.oneTime) {
parsedExpression.$$watchDelegate = parsedExpression.literal ?
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
} else if (parsedExpression.inputs) {
@@ -1811,6 +1823,12 @@ function $ParseProvider() {
}
}
function $$getAst(exp) {
var lexer = new Lexer($parseOptions);
var parser = new Parser(lexer, $filter, $parseOptions);
return parser.getAst(exp).ast;
}
function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
if (newValue == null || oldValueOfValue == null) { // null/undefined
+3 -6
View File
@@ -394,14 +394,15 @@ function $RootScopeProvider() {
*/
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
var get = $parse(watchExp);
var fn = isFunction(listener) ? listener : noop;
if (get.$$watchDelegate) {
return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
return get.$$watchDelegate(this, fn, objectEquality, get, watchExp);
}
var scope = this,
array = scope.$$watchers,
watcher = {
fn: listener,
fn: fn,
last: initWatchVal,
get: get,
exp: prettyPrintExpression || watchExp,
@@ -410,10 +411,6 @@ function $RootScopeProvider() {
lastDirtyWatch = null;
if (!isFunction(listener)) {
watcher.fn = noop;
}
if (!array) {
array = scope.$$watchers = [];
array.$$digestWatchIndex = -1;
+2 -2
View File
@@ -34,7 +34,7 @@ function parseTextLiteral(text) {
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
var unwatch = scope['$watch'](noop,
function textLiteralWatcher() {
if (isFunction(listener)) { listener(text, text, scope); }
listener(text, text, scope);
unwatch();
},
objectEquality);
@@ -58,7 +58,7 @@ function subtractOffset(expressionFn, offset) {
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
unwatch = scope['$watch'](expressionFn,
function pluralExpressionWatchListener(newValue, oldValue) {
if (isFunction(listener)) { listener(minusOffset(newValue), minusOffset(oldValue), scope); }
listener(minusOffset(newValue), minusOffset(oldValue), scope);
},
objectEquality);
return unwatch;
@@ -122,9 +122,7 @@ function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEq
InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
var result = this.interpolationParts.getResult(newExpressionValues);
if (isFunction(this.listener)) {
this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
}
this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
this.previousResult = result;
};
+1 -3
View File
@@ -66,9 +66,7 @@ MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnLi
};
MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
if (isFunction(this.listener)) {
this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
}
this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
this.lastMessage = newMessage;
};
+48 -5
View File
@@ -2,13 +2,56 @@
(function() {
/**
* Triggers a browser event. Attempts to choose the right event if one is
* not specified.
* @ngdoc function
* @name browserTrigger
* @description
*
* This is a global (window) function that is only available when the {@link ngMock} module is
* included.
*
* It can be used to trigger a native browser event on an element, which is useful for unit testing.
*
*
* @param {Object} element Either a wrapped jQuery/jqLite node or a DOMElement
* @param {string} eventType Optional event type
* @param {Object=} eventData An optional object which contains additional event data (such as x,y
* coordinates, keys, etc...) that are passed into the event when triggered
* @param {string=} eventType Optional event type. If none is specified, the function tries
* to determine the right event type for the element, e.g. `change` for
* `input[text]`.
* @param {Object=} eventData An optional object which contains additional event data that is used
* when creating the event:
*
* - `bubbles`: [Event.bubbles](https://developer.mozilla.org/docs/Web/API/Event/bubbles).
* Not applicable to all events.
*
* - `cancelable`: [Event.cancelable](https://developer.mozilla.org/docs/Web/API/Event/cancelable).
* Not applicable to all events.
*
* - `charcode`: [charCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/charcode)
* for keyboard events (keydown, keypress, and keyup).
*
* - `elapsedTime`: the elapsedTime for
* [TransitionEvent](https://developer.mozilla.org/docs/Web/API/TransitionEvent)
* and [AnimationEvent](https://developer.mozilla.org/docs/Web/API/AnimationEvent).
*
* - `keycode`: [keyCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/keycode)
* for keyboard events (keydown, keypress, and keyup).
*
* - `keys`: an array of possible modifier keys (ctrl, alt, shift, meta) for
* [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent) and
* keyboard events (keydown, keypress, and keyup).
*
* - `relatedTarget`: the
* [relatedTarget](https://developer.mozilla.org/docs/Web/API/MouseEvent/relatedTarget)
* for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent).
*
* - `which`: [which](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/which)
* for keyboard events (keydown, keypress, and keyup).
*
* - `x`: x-coordinates for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent)
* and [TouchEvent](https://developer.mozilla.org/docs/Web/API/TouchEvent).
*
* - `y`: y-coordinates for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent)
* and [TouchEvent](https://developer.mozilla.org/docs/Web/API/TouchEvent).
*
*/
window.browserTrigger = function browserTrigger(element, eventType, eventData) {
if (element && !element.nodeName) element = element[0];
+37
View File
@@ -391,6 +391,43 @@ describe('ngModelOptions', function() {
browserTrigger(inputElm[2], 'click');
expect($rootScope.color).toBe('blue');
});
it('should re-set the trigger events when overridden with $overrideModelOptions', function() {
var inputElm = helper.compileInput(
'<input type="text" ng-model="name" name="alias" ' +
'ng-model-options="{ updateOn: \'blur click\' }"' +
'/>');
var ctrl = inputElm.controller('ngModel');
helper.changeInputValueTo('a');
expect($rootScope.name).toBeUndefined();
browserTrigger(inputElm, 'blur');
expect($rootScope.name).toEqual('a');
helper.changeInputValueTo('b');
expect($rootScope.name).toBe('a');
browserTrigger(inputElm, 'click');
expect($rootScope.name).toEqual('b');
$rootScope.$apply('name = undefined');
expect(inputElm.val()).toBe('');
ctrl.$overrideModelOptions({updateOn: 'blur mousedown'});
helper.changeInputValueTo('a');
expect($rootScope.name).toBeUndefined();
browserTrigger(inputElm, 'blur');
expect($rootScope.name).toEqual('a');
helper.changeInputValueTo('b');
expect($rootScope.name).toBe('a');
browserTrigger(inputElm, 'click');
expect($rootScope.name).toBe('a');
browserTrigger(inputElm, 'mousedown');
expect($rootScope.name).toEqual('b');
});
});
+14
View File
@@ -673,6 +673,20 @@ describe('$location', function() {
locationUrl.search({'q': '4/5 6'});
expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206');
});
it('url() should decode non-component special characters in hashbang mode', function() {
var locationUrl = new LocationHashbangUrl('http://host.com', 'http://host.com');
locationUrl.$$parse('http://host.com');
locationUrl.url('/foo%3Abar');
expect(locationUrl.path()).toEqual('/foo:bar');
});
it('url() should decode non-component special characters in html5 mode', function() {
var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com');
locationUrl.$$parse('http://host.com');
locationUrl.url('/foo%3Abar');
expect(locationUrl.path()).toEqual('/foo:bar');
});
});
});
+44
View File
@@ -3994,4 +3994,48 @@ describe('parser', function() {
});
});
});
describe('hidden/unsupported features', function() {
describe('$$getAst()', function() {
it('should be a method exposed on the `$parse` service', inject(function($parse) {
expect(isFunction($parse.$$getAst)).toBeTruthy();
}));
it('should accept a string expression argument and return the corresponding AST', inject(function($parse) {
var ast = $parse.$$getAst('foo.bar');
expect(ast).toEqual({
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'MemberExpression',
object: { type: 'Identifier', name: 'foo' },
property: { type: 'Identifier', name: 'bar' },
computed: false
}
}
]
});
}));
it('should parse one time binding expressions', inject(function($parse) {
var ast = $parse.$$getAst('::foo.bar');
expect(ast).toEqual({
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'MemberExpression',
object: { type: 'Identifier', name: 'foo' },
property: { type: 'Identifier', name: 'bar' },
computed: false
}
}
]
});
}));
});
});
});
+39
View File
@@ -2390,6 +2390,45 @@ describe('Scope', function() {
}));
});
});
it('should allow recursive $emit/$broadcast', inject(function($rootScope) {
var callCount = 0;
$rootScope.$on('evt', function($event, arg0) {
callCount++;
if (arg0 !== 1234) {
$rootScope.$emit('evt', 1234);
$rootScope.$broadcast('evt', 1234);
}
});
$rootScope.$emit('evt');
$rootScope.$broadcast('evt');
expect(callCount).toBe(6);
}));
it('should allow recursive $emit/$broadcast between parent/child', inject(function($rootScope) {
var child = $rootScope.$new();
var calls = '';
$rootScope.$on('evt', function($event, arg0) {
calls += 'r'; // For "root".
if (arg0 === 'fromChild') {
$rootScope.$broadcast('evt', 'fromRoot2');
}
});
child.$on('evt', function($event, arg0) {
calls += 'c'; // For "child".
if (arg0 === 'fromRoot1') {
child.$emit('evt', 'fromChild');
}
});
$rootScope.$broadcast('evt', 'fromRoot1');
expect(calls).toBe('rccrrc');
}));
});
describe('doc examples', function() {