Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93a754a490 | |||
| 7d9d387195 | |||
| feac52d840 | |||
| f4f571efdf | |||
| a8c263c194 | |||
| 3d6c45d76e | |||
| bf841d3512 | |||
| a1d88457de | |||
| b011ae9544 | |||
| 63fdee6b9c | |||
| 257ebbb514 | |||
| 2b6c986736 | |||
| 2da4950406 | |||
| 789db83a8a | |||
| ce443792c4 | |||
| dd47867bfb | |||
| f2ebb82ba5 | |||
| 12698755be | |||
| f7d7954904 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# http://editorconfig.org
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
<a name="1.7.1"></a>
|
||||
# 1.7.1 momentum-defiance (2018-06-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** support transcluding multi-element directives
|
||||
([789db8](https://github.com/angular/angular.js/commit/789db83a8ae0e2db5db13289b2c29e56093d967a),
|
||||
[#15554](https://github.com/angular/angular.js.git/issues/15554),
|
||||
[#15555](https://github.com/angular/angular.js.git/issues/15555))
|
||||
- **ngModel:** do not throw if view value changes on destroyed scope
|
||||
([2b6c98](https://github.com/angular/angular.js/commit/2b6c9867369fd3ef1ddb687af1153478ab62ee1b),
|
||||
[#16583](https://github.com/angular/angular.js.git/issues/16583),
|
||||
[#16585](https://github.com/angular/angular.js.git/issues/16585))
|
||||
|
||||
|
||||
## New Features
|
||||
- **$compile:** add one-way collection bindings
|
||||
([f9d1ca](https://github.com/angular/angular.js/commit/f9d1ca20c38f065f15769fbe23aee5314cb58bd4),
|
||||
[#14039](https://github.com/angular/angular.js.git/issues/14039),
|
||||
[#16553](https://github.com/angular/angular.js.git/issues/16553),
|
||||
[#15874](https://github.com/angular/angular.js.git/issues/15874))
|
||||
- **ngRef:** add directive to publish controller, or element into scope
|
||||
([bf841d](https://github.com/angular/angular.js/commit/bf841d35120bf3c4655fde46af4105c85a0f1cdc),
|
||||
[#16511](https://github.com/angular/angular.js.git/issues/16511))
|
||||
- **errorHandlingConfig:** add option to exclude error params from url
|
||||
([3d6c45](https://github.com/angular/angular.js/commit/3d6c45d76e30b1b3c4eb9672cf4a93e5251c06b3),
|
||||
[#14744](https://github.com/angular/angular.js.git/issues/14744),
|
||||
[#15707](https://github.com/angular/angular.js.git/issues/15707),
|
||||
[#16283](https://github.com/angular/angular.js.git/issues/16283),
|
||||
[#16299](https://github.com/angular/angular.js.git/issues/16299),
|
||||
[#16591](https://github.com/angular/angular.js.git/issues/16591))
|
||||
- **ngAria:** add support for ignoring a specific element
|
||||
([7d9d38](https://github.com/angular/angular.js/commit/7d9d387195292cb5e04984602b752d31853cfea6),
|
||||
[#14602](https://github.com/angular/angular.js.git/issues/14602),
|
||||
[#14672](https://github.com/angular/angular.js.git/issues/14672),
|
||||
[#14833](https://github.com/angular/angular.js.git/issues/14833))
|
||||
- **ngCookies:** support samesite option
|
||||
([10a229](https://github.com/angular/angular.js/commit/10a229ce1befdeaf6295d1635dc11391c252a91a),
|
||||
[#16543](https://github.com/angular/angular.js.git/issues/16543),
|
||||
[#16544](https://github.com/angular/angular.js.git/issues/16544))
|
||||
- **ngMessages:** add support for default message
|
||||
([a8c263](https://github.com/angular/angular.js/commit/a8c263c1947cc85ee60b4732f7e4bcdc7ba463e8),
|
||||
[#12008](https://github.com/angular/angular.js.git/issues/12008),
|
||||
[#12213](https://github.com/angular/angular.js.git/issues/12213),
|
||||
[#16587](https://github.com/angular/angular.js.git/issues/16587))
|
||||
- **ngMock, ngMockE2E:** add option to match latest definition for `$httpBackend` request
|
||||
([773f39](https://github.com/angular/angular.js/commit/773f39c9345479f5f8b6321236ce6ad96f77aa92),
|
||||
[#16251](https://github.com/angular/angular.js.git/issues/16251),
|
||||
[#11637](https://github.com/angular/angular.js.git/issues/11637),
|
||||
[#16560](https://github.com/angular/angular.js.git/issues/16560))
|
||||
- **$route:** add support for the `reloadOnUrl` configuration option
|
||||
([f4f571](https://github.com/angular/angular.js/commit/f4f571efdf86d6acbcd5c6b1de66b4b33a259125),
|
||||
[#7925](https://github.com/angular/angular.js.git/issues/7925),
|
||||
[#15002](https://github.com/angular/angular.js.git/issues/15002))
|
||||
|
||||
|
||||
<a name="1.7.0"></a>
|
||||
# 1.7.0 nonexistent-physiology (2018-05-11)
|
||||
|
||||
|
||||
Vendored
+1
@@ -74,6 +74,7 @@ var angularFiles = {
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRef.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
'src/ng/directive/ngStyle.js',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name ngRef:noctrl
|
||||
@fullName A controller for the value of `ngRefRead` could not be found on the element.
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ng.ngRef ngRef directive} specifies
|
||||
a value in `ngRefRead` that cannot be resolved to a directive / component controller.
|
||||
|
||||
Causes for this error can be:
|
||||
|
||||
1. Your `ngRefRead` value has a typo.
|
||||
2. You have a typo in the *registered* directive / component name.
|
||||
3. The directive / component does not have a controller.
|
||||
|
||||
Note that `ngRefRead` takes the name of the component / directive, not the name of controller, and
|
||||
also not the combination of directive and 'Controller'. For example, for a directive called 'myDirective',
|
||||
the correct declaration is `<div ng-ref="$ctrl.ref" ng-ref-read="myDirective">`.
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc error
|
||||
@name ngRef:nonassign
|
||||
@fullName Non-Assignable Expression
|
||||
@description
|
||||
|
||||
This error occurs when ngRef defines an expression that is not-assignable.
|
||||
|
||||
In order for ngRef to work, it must be possible to write the reference into the path defined with the expression.
|
||||
|
||||
For example, the following expressions are non-assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="{}"></my-directive>
|
||||
|
||||
<my-directive ng-ref="myFn()"></my-directive>
|
||||
|
||||
<!-- missing attribute value is also invalid -->
|
||||
<my-directive ng-ref></my-directive>
|
||||
|
||||
```
|
||||
|
||||
To resolve this error, use a path expression that is assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="$ctrl.reference"></my-directive>
|
||||
|
||||
```
|
||||
@@ -222,23 +222,26 @@ triggered:
|
||||
|
||||
| Directive | Supported Animations |
|
||||
|-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
| {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
| {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
| {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
| {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
|
||||
(More information can be found by visiting the documentation associated with each directive.)
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the
|
||||
{@link ng.$animate API docs}.
|
||||
{@link ng.$animate `$animate` API docs}.
|
||||
|
||||
## How do I use animations in my own directives?
|
||||
|
||||
|
||||
@@ -107,12 +107,14 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_iOS_10': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.12',
|
||||
version: '10.3'
|
||||
},
|
||||
'SL_iOS_11': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
version: '11'
|
||||
platform: 'OS X 10.12',
|
||||
version: '11.2'
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -194,6 +195,7 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
|
||||
+4
-26
@@ -70,12 +70,8 @@ var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
|
||||
function stringifyFn(fn) {
|
||||
return Function.prototype.toString.call(fn);
|
||||
}
|
||||
|
||||
function extractArgs(fn) {
|
||||
var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
|
||||
var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
|
||||
return args;
|
||||
}
|
||||
@@ -914,19 +910,6 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
return args;
|
||||
}
|
||||
|
||||
function isClass(func) {
|
||||
// Support: IE 9-11 only
|
||||
// IE 9-11 do not support classes and IE9 leaks with the code below.
|
||||
if (msie || typeof func !== 'function') {
|
||||
return false;
|
||||
}
|
||||
var result = func.$$ngIsClass;
|
||||
if (!isBoolean(result)) {
|
||||
result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function invoke(fn, self, locals, serviceName) {
|
||||
if (typeof locals === 'string') {
|
||||
serviceName = locals;
|
||||
@@ -938,14 +921,9 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
fn = fn[fn.length - 1];
|
||||
}
|
||||
|
||||
if (!isClass(fn)) {
|
||||
// http://jsperf.com/angularjs-invoke-apply-vs-switch
|
||||
// #5388
|
||||
return fn.apply(self, args);
|
||||
} else {
|
||||
args.unshift(null);
|
||||
return new (Function.prototype.bind.apply(fn, args))();
|
||||
}
|
||||
// http://jsperf.com/angularjs-invoke-apply-vs-switch
|
||||
// #5388
|
||||
return fn.apply(self, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+16
-3
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
var minErrConfig = {
|
||||
objectMaxDepth: 5
|
||||
objectMaxDepth: 5,
|
||||
urlErrorParamsEnabled: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,12 +31,21 @@ var minErrConfig = {
|
||||
* * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
|
||||
* non-positive or non-numeric value, removes the max depth limit.
|
||||
* Default: 5
|
||||
*
|
||||
* * `urlErrorParamsEnabled` **{Boolean}** - Specifies wether the generated error url will
|
||||
* contain the parameters of the thrown error. Disabling the parameters can be useful if the
|
||||
* generated error url is very long.
|
||||
*
|
||||
* Default: true. When used without argument, it returns the current value.
|
||||
*/
|
||||
function errorHandlingConfig(config) {
|
||||
if (isObject(config)) {
|
||||
if (isDefined(config.objectMaxDepth)) {
|
||||
minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
|
||||
}
|
||||
if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) {
|
||||
minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled;
|
||||
}
|
||||
} else {
|
||||
return minErrConfig;
|
||||
}
|
||||
@@ -50,6 +60,7 @@ function isValidObjectMaxDepth(maxDepth) {
|
||||
return isNumber(maxDepth) && maxDepth > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
@@ -113,8 +124,10 @@ function minErr(module, ErrorConstructor) {
|
||||
|
||||
message += '\n' + url + (module ? module + '/' : '') + code;
|
||||
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
if (minErrConfig.urlErrorParamsEnabled) {
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
|
||||
+66
-80
@@ -2575,7 +2575,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = [];
|
||||
$template = window.document.createDocumentFragment();
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
@@ -2603,10 +2603,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
|
||||
slots[slotName].appendChild(node);
|
||||
} else {
|
||||
$template.push(node);
|
||||
$template.appendChild(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2620,9 +2620,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName].childNodes, transcludeFn);
|
||||
}
|
||||
}
|
||||
|
||||
$template = $template.childNodes;
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
@@ -2788,10 +2790,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
|
||||
}
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
// Initialize isolate scope bindings for new isolate scope directive.
|
||||
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
|
||||
@@ -2807,53 +2805,69 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bindToController bindings
|
||||
for (var name in elementControllers) {
|
||||
var controllerDirective = controllerDirectives[name];
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
if (controllerDirectives) {
|
||||
elementControllers = createMap();
|
||||
for (var name in controllerDirectives) {
|
||||
var directive = controllerDirectives[name];
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
};
|
||||
|
||||
controller.instance = controller();
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controller.instance);
|
||||
controller.bindingInfo =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
|
||||
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
|
||||
forEach(controllerDirectives, function(controllerDirective, name) {
|
||||
var require = controllerDirective.require;
|
||||
if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
|
||||
extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the init and destroy lifecycle hooks on all controllers that have them
|
||||
forEach(elementControllers, function(controller) {
|
||||
var controllerInstance = controller.instance;
|
||||
if (isFunction(controllerInstance.$onChanges)) {
|
||||
try {
|
||||
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
var controllerConstructor = directive.controller;
|
||||
if (controllerConstructor === '@') {
|
||||
controllerConstructor = attrs[name];
|
||||
}
|
||||
|
||||
var instance = $controller(controllerConstructor, locals, directive.controllerAs);
|
||||
|
||||
$element.data('$' + name + 'Controller', instance);
|
||||
|
||||
// Initialize bindToController bindings
|
||||
var bindings = directive.$$bindings.bindToController;
|
||||
var bindingInfo = initializeDirectiveBindings(controllerScope, attrs, instance, bindings, directive);
|
||||
|
||||
elementControllers[name] = { instance: instance, bindingInfo: bindingInfo };
|
||||
}
|
||||
if (isFunction(controllerInstance.$onInit)) {
|
||||
try {
|
||||
controllerInstance.$onInit();
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
|
||||
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
|
||||
forEach(controllerDirectives, function(controllerDirective, name) {
|
||||
var require = controllerDirective.require;
|
||||
if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
|
||||
extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
|
||||
}
|
||||
}
|
||||
if (isFunction(controllerInstance.$doCheck)) {
|
||||
controllerScope.$watch(function() { controllerInstance.$doCheck(); });
|
||||
controllerInstance.$doCheck();
|
||||
}
|
||||
if (isFunction(controllerInstance.$onDestroy)) {
|
||||
controllerScope.$on('$destroy', function callOnDestroyHook() {
|
||||
controllerInstance.$onDestroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle the init and destroy lifecycle hooks on all controllers that have them
|
||||
forEach(elementControllers, function(controller) {
|
||||
var controllerInstance = controller.instance;
|
||||
if (isFunction(controllerInstance.$onChanges)) {
|
||||
try {
|
||||
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
if (isFunction(controllerInstance.$onInit)) {
|
||||
try {
|
||||
controllerInstance.$onInit();
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
if (isFunction(controllerInstance.$doCheck)) {
|
||||
controllerScope.$watch(function() { controllerInstance.$doCheck(); });
|
||||
controllerInstance.$doCheck();
|
||||
}
|
||||
if (isFunction(controllerInstance.$onDestroy)) {
|
||||
controllerScope.$on('$destroy', function callOnDestroyHook() {
|
||||
controllerInstance.$onDestroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PRELINKING
|
||||
for (i = 0, ii = preLinkFns.length; i < ii; i++) {
|
||||
@@ -2981,34 +2995,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
|
||||
var elementControllers = createMap();
|
||||
for (var controllerKey in controllerDirectives) {
|
||||
var directive = controllerDirectives[controllerKey];
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
};
|
||||
|
||||
var controller = directive.controller;
|
||||
if (controller === '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
|
||||
|
||||
// For directives with element transclusion the element is a comment.
|
||||
// In this case .data will not attach any data.
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
// Depending upon the context in which a directive finds itself it might need to have a new isolated
|
||||
// or child scope created. For instance:
|
||||
// * if the directive has been pulled into a template because another directive with a higher priority
|
||||
|
||||
+1
-41
@@ -81,16 +81,11 @@ function $ControllerProvider() {
|
||||
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
|
||||
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
|
||||
*/
|
||||
return function $controller(expression, locals, later, ident) {
|
||||
return function $controller(expression, locals, ident) {
|
||||
// PRIVATE API:
|
||||
// param `later` --- indicates that the controller's constructor is invoked at a later time.
|
||||
// If true, $controller will allocate the object with the correct
|
||||
// prototype chain, but will not invoke the controller until a returned
|
||||
// callback is invoked.
|
||||
// param `ident` --- An optional label which overrides the label parsed from the controller
|
||||
// expression, if any.
|
||||
var instance, match, constructor, identifier;
|
||||
later = later === true;
|
||||
if (ident && isString(ident)) {
|
||||
identifier = ident;
|
||||
}
|
||||
@@ -116,41 +111,6 @@ function $ControllerProvider() {
|
||||
assertArgFn(expression, constructor, true);
|
||||
}
|
||||
|
||||
if (later) {
|
||||
// Instantiate controller later:
|
||||
// This machinery is used to create an instance of the object before calling the
|
||||
// controller's constructor itself.
|
||||
//
|
||||
// This allows properties to be added to the controller before the constructor is
|
||||
// invoked. Primarily, this is used for isolate scope bindings in $compile.
|
||||
//
|
||||
// This feature is not intended for use by applications, and is thus not documented
|
||||
// publicly.
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var controllerPrototype = (isArray(expression) ?
|
||||
expression[expression.length - 1] : expression).prototype;
|
||||
instance = Object.create(controllerPrototype || null);
|
||||
|
||||
if (identifier) {
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
}
|
||||
|
||||
return extend(function $controllerInit() {
|
||||
var result = $injector.invoke(expression, instance, locals, constructor);
|
||||
if (result !== instance && (isObject(result) || isFunction(result))) {
|
||||
instance = result;
|
||||
if (identifier) {
|
||||
// If result changed, re-assign controllerAs value to scope.
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}, {
|
||||
instance: instance,
|
||||
identifier: identifier
|
||||
});
|
||||
}
|
||||
|
||||
instance = $injector.instantiate(expression, locals, constructor);
|
||||
|
||||
if (identifier) {
|
||||
|
||||
@@ -287,6 +287,7 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
|
||||
this.$$currentValidationRunId = 0;
|
||||
|
||||
this.$$scope = $scope;
|
||||
this.$$rootScope = $scope.$root;
|
||||
this.$$attr = $attr;
|
||||
this.$$element = $element;
|
||||
this.$$animate = $animate;
|
||||
@@ -864,7 +865,7 @@ NgModelController.prototype = {
|
||||
this.$$pendingDebounce = this.$$timeout(function() {
|
||||
that.$commitViewValue();
|
||||
}, debounceDelay);
|
||||
} else if (this.$$scope.$root.$$phase) {
|
||||
} else if (this.$$rootScope.$$phase) {
|
||||
this.$commitViewValue();
|
||||
} else {
|
||||
this.$$scope.$apply(function() {
|
||||
|
||||
@@ -41,7 +41,7 @@ ModelOptions.prototype = {
|
||||
options = extend({}, options);
|
||||
|
||||
// Inherit options from the parent if specified by the value `"$inherit"`
|
||||
forEach(options, /* @this */ function(option, key) {
|
||||
forEach(options, /** @this */ function(option, key) {
|
||||
if (option === '$inherit') {
|
||||
if (key === '*') {
|
||||
inheritAll = true;
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngRef
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
* The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
|
||||
* to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
|
||||
* element to the scope.
|
||||
*
|
||||
* If the element with `ngRef` is destroyed `null` is assigned to the property.
|
||||
*
|
||||
* Note that if you want to assign from a child into the parent scope, you must initialize the
|
||||
* target property on the parent scope, otherwise `ngRef` will assign on the child scope.
|
||||
* This commonly happens when assigning elements or components wrapped in {@link ngIf} or
|
||||
* {@link ngRepeat}. See the second example below.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} ngRef property name - A valid AngularJS expression identifier to which the
|
||||
* controller or jqlite-wrapped DOM element will be bound.
|
||||
* @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
|
||||
* or the special string `$element`. If a name is provided, `ngRef` will
|
||||
* assign the matching controller. If `$element` is provided, the element
|
||||
* itself is assigned (even if a controller is available).
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Simple toggle
|
||||
* This example shows how the controller of the component toggle
|
||||
* is reused in the template through the scope to use its logic.
|
||||
* <example name="ng-ref-component" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-toggle ng-ref="myToggle"></my-toggle>
|
||||
* <button ng-click="myToggle.toggle()">Toggle</button>
|
||||
* <div ng-show="myToggle.isOpen()">
|
||||
* You are using a component in the same template to show it.
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myToggle', {
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should publish the toggle into the scope', function() {
|
||||
* var toggle = element(by.buttonText('Toggle'));
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
|
||||
* toggle.click();
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### ngRef inside scopes
|
||||
* This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
|
||||
* are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
|
||||
* The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
|
||||
* `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
|
||||
* because the target property has not been initialized on the `myRoot` component controller.
|
||||
*
|
||||
* <example name="ng-ref-scopes" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-root></my-root>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myRoot', {
|
||||
* templateUrl: 'root.html',
|
||||
* controller: function() {
|
||||
* this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
|
||||
* }
|
||||
* })
|
||||
* .component('myToggle', {
|
||||
* template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
|
||||
* transclude: true,
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* })
|
||||
* .component('myWrapper', {
|
||||
* transclude: true,
|
||||
* template: '<strong>myWrapper</strong>' +
|
||||
* '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
|
||||
* '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
|
||||
* });
|
||||
* </file>
|
||||
* <file name="root.html">
|
||||
* <strong>myRoot</strong>
|
||||
* <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <div><em>wrappers assigned to root</em><br>
|
||||
* <div ng-repeat="wrapper in $ctrl.wrappers">
|
||||
* wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
|
||||
* </div>
|
||||
*
|
||||
* <ul>
|
||||
* <li ng-repeat="(index, value) in [1,2,3]">
|
||||
* <strong>ngRepeat</strong>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
|
||||
* assigned to the child scope created by ngIf.
|
||||
* </div>
|
||||
* <div ng-if="true">
|
||||
<strong>ngIf</strong>
|
||||
* <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* ul {
|
||||
* list-style: none;
|
||||
* padding-left: 0;
|
||||
* }
|
||||
*
|
||||
* li[ng-repeat] {
|
||||
* background: lightgreen;
|
||||
* padding: 8px;
|
||||
* margin: 8px;
|
||||
* }
|
||||
*
|
||||
* [ng-if] {
|
||||
* background: lightgrey;
|
||||
* padding: 8px;
|
||||
* }
|
||||
*
|
||||
* my-root {
|
||||
* background: lightgoldenrodyellow;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-wrapper {
|
||||
* background: lightsalmon;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-toggle {
|
||||
* background: lightblue;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var OuterToggle = function() {
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('Outer Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return element.all(by.binding('outerToggle.isOpen()')).first().getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggle = function(i) {
|
||||
* var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1);
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngRepeat Toggle ' + i)).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggles = function() {
|
||||
* var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
|
||||
* this.forEach = function(fn) {
|
||||
* toggles.forEach(fn);
|
||||
* };
|
||||
* this.isOuterOpen = function(i) {
|
||||
* return toggles[i - 1].isOuterOpen();
|
||||
* };
|
||||
* };
|
||||
* var NgIfToggle = function() {
|
||||
* var parent = element(by.css('[ng-if]'));
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngIf Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return by.binding('ngIfToggle.isOpen() | json').getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* it('should toggle the outer toggle', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle all outer toggles', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
* var ifToggle = new NgIfToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle each repeat iteration separately', function() {
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
*
|
||||
* repeatToggles.forEach(function(repeatToggle) {
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* repeatToggle.toggle();
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* });
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
var ngRefMinErr = minErr('ngRef');
|
||||
|
||||
var ngRefDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
priority: -1, // Needed for compatibility with element transclusion on the same element
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
// Get the expected controller name, converts <data-some-thing> into "someThing"
|
||||
var controllerName = directiveNormalize(nodeName_(tElement));
|
||||
|
||||
// Get the expression for value binding
|
||||
var getter = $parse(tAttrs.ngRef);
|
||||
var setter = getter.assign || function() {
|
||||
throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
|
||||
};
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var refValue;
|
||||
|
||||
if (attrs.hasOwnProperty('ngRefRead')) {
|
||||
if (attrs.ngRefRead === '$element') {
|
||||
refValue = element;
|
||||
} else {
|
||||
refValue = element.data('$' + attrs.ngRefRead + 'Controller');
|
||||
|
||||
if (!refValue) {
|
||||
throw ngRefMinErr(
|
||||
'noctrl',
|
||||
'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
|
||||
attrs.ngRefRead,
|
||||
tAttrs.ngRef
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refValue = element.data('$' + controllerName + 'Controller');
|
||||
}
|
||||
|
||||
refValue = refValue || element;
|
||||
|
||||
setter(scope, refValue);
|
||||
|
||||
// when the element is removed, remove it (nullify it)
|
||||
element.on('$destroy', function() {
|
||||
// only remove it if value has not changed,
|
||||
// because animations (and other procedures) may duplicate elements
|
||||
if (getter(scope) === refValue) {
|
||||
setter(scope, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
+35
-14
@@ -17,20 +17,28 @@
|
||||
* ## Directive Support
|
||||
* The following directives are "animation aware":
|
||||
*
|
||||
* | Directive | Supported Animations |
|
||||
* |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
|
||||
* | {@link ng.directive:form#animations form} & {@link ng.directive:ngModel#animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
|
||||
* | {@link module:ngMessages#animations ngMessage} | enter and leave |
|
||||
* | Directive | Supported Animations |
|
||||
* |-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
* | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
* | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
* | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
* | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
* | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
*
|
||||
* (More information can be found by visiting each the documentation associated with each directive.)
|
||||
* (More information can be found by visiting the documentation associated with each directive.)
|
||||
*
|
||||
* For a full breakdown of the steps involved during each animation event, refer to the
|
||||
* {@link ng.$animate `$animate` API docs}.
|
||||
*
|
||||
* ## CSS-based Animations
|
||||
*
|
||||
@@ -267,9 +275,22 @@
|
||||
* .message.ng-enter-prepare {
|
||||
* opacity: 0;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Animating between value changes
|
||||
*
|
||||
* Sometimes you need to animate between different expression states, whose values
|
||||
* don't necessary need to be known or referenced in CSS styles.
|
||||
* Unless possible with another ["animation aware" directive](#directive-support), that specific
|
||||
* use case can always be covered with {@link ngAnimate.directive:ngAnimateSwap} as can be seen in
|
||||
* {@link ngAnimate.directive:ngAnimateSwap#examples this example}.
|
||||
*
|
||||
* Note that {@link ngAnimate.directive:ngAnimateSwap} is a *structural directive*, which means it
|
||||
* creates a new instance of the element (including any other/child directives it may have) and
|
||||
* links it to a new scope every time *swap* happens. In some cases this might not be desirable
|
||||
* (e.g. for performance reasons, or when you wish to retain internal state on the original
|
||||
* element instance).
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
|
||||
+20
-4
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
|
||||
* directives are supported:
|
||||
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
|
||||
* `ngDblClick`, and `ngMessages`.
|
||||
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`,
|
||||
* `ngClick`, `ngDblClick`, and `ngMessages`.
|
||||
*
|
||||
* Below is a more detailed breakdown of the attributes handled by ngAria:
|
||||
*
|
||||
@@ -46,11 +46,17 @@
|
||||
* <md-checkbox ng-disabled="disabled" aria-disabled="true">
|
||||
* ```
|
||||
*
|
||||
* ## Disabling Attributes
|
||||
* It's possible to disable individual attributes added by ngAria with the
|
||||
* ## Disabling Specific Attributes
|
||||
* It is possible to disable individual attributes added by ngAria with the
|
||||
* {@link ngAria.$ariaProvider#config config} method. For more details, see the
|
||||
* {@link guide/accessibility Developer Guide}.
|
||||
*
|
||||
* ## Disabling `ngAria` on Specific Elements
|
||||
* It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable`
|
||||
* attribute on it. Note that only the element itself (and not its child elements) will be ignored.
|
||||
*/
|
||||
var ARIA_DISABLE_ATTR = 'ngAriaDisable';
|
||||
|
||||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
info({ angularVersion: '"NG_VERSION_FULL"' }).
|
||||
provider('$aria', $AriaProvider);
|
||||
@@ -132,6 +138,8 @@ function $AriaProvider() {
|
||||
|
||||
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
|
||||
return function(scope, elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
@@ -251,6 +259,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
require: 'ngModel',
|
||||
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
|
||||
compile: function(elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var shape = getShape(attr, elem);
|
||||
|
||||
return {
|
||||
@@ -347,6 +357,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
restrict: 'A',
|
||||
require: '?ngMessages',
|
||||
link: function(scope, elem, attr, ngMessages) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
if (!elem.attr('aria-live')) {
|
||||
elem.attr('aria-live', 'assertive');
|
||||
}
|
||||
@@ -357,6 +369,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function(elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
var fn = $parse(attr.ngClick);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
@@ -389,6 +403,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
+128
-46
@@ -18,7 +18,7 @@ var jqLite;
|
||||
* sequencing based on the order of how the messages are defined in the template.
|
||||
*
|
||||
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
|
||||
* `ngMessage` and `ngMessageExp` directives.
|
||||
* `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
|
||||
*
|
||||
* ## Usage
|
||||
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
|
||||
@@ -257,7 +257,26 @@ var jqLite;
|
||||
* .some-message.ng-leave.ng-leave-active {}
|
||||
* ```
|
||||
*
|
||||
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
|
||||
* {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn
|
||||
* more about ngAnimate.
|
||||
*
|
||||
* ## Displaying a default message
|
||||
* If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy
|
||||
* keys are matched by a defined message), then it will render a default message
|
||||
* using the {@link ngMessageDefault} directive.
|
||||
* Note that matched messages will always take precedence over unmatched messages. That means
|
||||
* the default message will not be displayed when another message is matched. This is also
|
||||
* true for `ng-messages-multiple`.
|
||||
*
|
||||
* ```html
|
||||
* <div ng-messages="myForm.myField.$error" role="alert">
|
||||
* <div ng-message="required">This field is required</div>
|
||||
* <div ng-message="minlength">This field is too short</div>
|
||||
* <div ng-message-default>This field has an input error</div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
|
||||
*/
|
||||
angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
// Access helpers from AngularJS core.
|
||||
@@ -286,8 +305,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* at a time and this depends on the prioritization of the messages within the template. (This can
|
||||
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
|
||||
*
|
||||
* A remote template can also be used to promote message reusability and messages can also be
|
||||
* overridden.
|
||||
* A remote template can also be used (With {@link ngMessagesInclude}) to promote message
|
||||
* reusability and messages can also be overridden.
|
||||
*
|
||||
* A default message can also be displayed when no `ngMessage` directive is inserted, using the
|
||||
* {@link ngMessageDefault} directive.
|
||||
*
|
||||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
|
||||
*
|
||||
@@ -298,6 +320,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* <ANY ng-message-exp="expressionValue">...</ANY>
|
||||
* <ANY ng-message-default>...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
@@ -305,6 +328,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* <ng-message when-exp="expressionValue">...</ng-message>
|
||||
* <ng-message-default>...</ng-message-default>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
@@ -333,6 +357,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
* <div ng-message="required">You did not enter a field</div>
|
||||
* <div ng-message="minlength">Your field is too short</div>
|
||||
* <div ng-message="maxlength">Your field is too long</div>
|
||||
* <div ng-message-default>This field has an input error</div>
|
||||
* </div>
|
||||
* </form>
|
||||
* </file>
|
||||
@@ -370,6 +395,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
|
||||
var unmatchedMessages = [];
|
||||
var matchedKeys = {};
|
||||
var truthyKeys = 0;
|
||||
var messageItem = ctrl.head;
|
||||
var messageFound = false;
|
||||
var totalMessages = 0;
|
||||
@@ -382,13 +408,17 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
var messageUsed = false;
|
||||
if (!messageFound) {
|
||||
forEach(collection, function(value, key) {
|
||||
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
|
||||
// this is to prevent the same error name from showing up twice
|
||||
if (matchedKeys[key]) return;
|
||||
matchedKeys[key] = true;
|
||||
if (truthy(value) && !messageUsed) {
|
||||
truthyKeys++;
|
||||
|
||||
messageUsed = true;
|
||||
messageCtrl.attach();
|
||||
if (messageCtrl.test(key)) {
|
||||
// this is to prevent the same error name from showing up twice
|
||||
if (matchedKeys[key]) return;
|
||||
matchedKeys[key] = true;
|
||||
|
||||
messageUsed = true;
|
||||
messageCtrl.attach();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -408,7 +438,16 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
messageCtrl.detach();
|
||||
});
|
||||
|
||||
if (unmatchedMessages.length !== totalMessages) {
|
||||
var messageMatched = unmatchedMessages.length !== totalMessages;
|
||||
var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0;
|
||||
|
||||
if (attachDefault) {
|
||||
ctrl.default.attach();
|
||||
} else if (ctrl.default) {
|
||||
ctrl.default.detach();
|
||||
}
|
||||
|
||||
if (messageMatched || attachDefault) {
|
||||
$animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
|
||||
} else {
|
||||
$animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
|
||||
@@ -428,23 +467,31 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
}
|
||||
};
|
||||
|
||||
this.register = function(comment, messageCtrl) {
|
||||
var nextKey = latestKey.toString();
|
||||
messages[nextKey] = {
|
||||
message: messageCtrl
|
||||
};
|
||||
insertMessageNode($element[0], comment, nextKey);
|
||||
comment.$$ngMessageNode = nextKey;
|
||||
latestKey++;
|
||||
this.register = function(comment, messageCtrl, isDefault) {
|
||||
if (isDefault) {
|
||||
ctrl.default = messageCtrl;
|
||||
} else {
|
||||
var nextKey = latestKey.toString();
|
||||
messages[nextKey] = {
|
||||
message: messageCtrl
|
||||
};
|
||||
insertMessageNode($element[0], comment, nextKey);
|
||||
comment.$$ngMessageNode = nextKey;
|
||||
latestKey++;
|
||||
}
|
||||
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
this.deregister = function(comment) {
|
||||
var key = comment.$$ngMessageNode;
|
||||
delete comment.$$ngMessageNode;
|
||||
removeMessageNode($element[0], comment, key);
|
||||
delete messages[key];
|
||||
this.deregister = function(comment, isDefault) {
|
||||
if (isDefault) {
|
||||
delete ctrl.default;
|
||||
} else {
|
||||
var key = comment.$$ngMessageNode;
|
||||
delete comment.$$ngMessageNode;
|
||||
removeMessageNode($element[0], comment, key);
|
||||
delete messages[key];
|
||||
}
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
@@ -647,9 +694,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
|
||||
*
|
||||
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
|
||||
*/
|
||||
.directive('ngMessageExp', ngMessageDirectiveFactory());
|
||||
.directive('ngMessageExp', ngMessageDirectiveFactory())
|
||||
|
||||
function ngMessageDirectiveFactory() {
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngMessageDefault
|
||||
* @restrict AE
|
||||
* @scope
|
||||
*
|
||||
* @description
|
||||
* `ngMessageDefault` is a directive with the purpose to show and hide a default message for
|
||||
* {@link ngMessages}, when none of provided messages matches.
|
||||
*
|
||||
* More information about using `ngMessageDefault` can be found in the
|
||||
* {@link module:ngMessages `ngMessages` module documentation}.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression" role="alert">
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* <ANY ng-message-default>...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression" role="alert">
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* <ng-message-default>...</ng-message-default>
|
||||
* </ng-messages>
|
||||
*
|
||||
*/
|
||||
.directive('ngMessageDefault', ngMessageDirectiveFactory(true));
|
||||
|
||||
function ngMessageDirectiveFactory(isDefault) {
|
||||
return ['$animate', function($animate) {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
@@ -658,25 +737,28 @@ function ngMessageDirectiveFactory() {
|
||||
terminal: true,
|
||||
require: '^^ngMessages',
|
||||
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
|
||||
var commentNode = element[0];
|
||||
var commentNode, records, staticExp, dynamicExp;
|
||||
|
||||
var records;
|
||||
var staticExp = attrs.ngMessage || attrs.when;
|
||||
var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
|
||||
var assignRecords = function(items) {
|
||||
records = items
|
||||
? (isArray(items)
|
||||
? items
|
||||
: items.split(/[\s,]+/))
|
||||
: null;
|
||||
ngMessagesCtrl.reRender();
|
||||
};
|
||||
if (!isDefault) {
|
||||
commentNode = element[0];
|
||||
staticExp = attrs.ngMessage || attrs.when;
|
||||
dynamicExp = attrs.ngMessageExp || attrs.whenExp;
|
||||
|
||||
if (dynamicExp) {
|
||||
assignRecords(scope.$eval(dynamicExp));
|
||||
scope.$watchCollection(dynamicExp, assignRecords);
|
||||
} else {
|
||||
assignRecords(staticExp);
|
||||
var assignRecords = function(items) {
|
||||
records = items
|
||||
? (isArray(items)
|
||||
? items
|
||||
: items.split(/[\s,]+/))
|
||||
: null;
|
||||
ngMessagesCtrl.reRender();
|
||||
};
|
||||
|
||||
if (dynamicExp) {
|
||||
assignRecords(scope.$eval(dynamicExp));
|
||||
scope.$watchCollection(dynamicExp, assignRecords);
|
||||
} else {
|
||||
assignRecords(staticExp);
|
||||
}
|
||||
}
|
||||
|
||||
var currentElement, messageCtrl;
|
||||
@@ -701,7 +783,7 @@ function ngMessageDirectiveFactory() {
|
||||
// If the message element was removed via a call to `detach` then `currentElement` will be null
|
||||
// So this handler only handles cases where something else removed the message element.
|
||||
if (currentElement && currentElement.$$attachId === $$attachId) {
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
ngMessagesCtrl.deregister(commentNode, isDefault);
|
||||
messageCtrl.detach();
|
||||
}
|
||||
newScope.$destroy();
|
||||
@@ -716,14 +798,14 @@ function ngMessageDirectiveFactory() {
|
||||
$animate.leave(elm);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, isDefault);
|
||||
|
||||
// We need to ensure that this directive deregisters itself when it no longer exists
|
||||
// Normally this is done when the attached element is destroyed; but if this directive
|
||||
// gets removed before we attach the message to the DOM there is nothing to watch
|
||||
// in which case we must deregister when the containing scope is destroyed.
|
||||
scope.$on('$destroy', function() {
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
ngMessagesCtrl.deregister(commentNode, isDefault);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Vendored
+71
-28
@@ -1558,7 +1558,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1573,7 +1574,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1588,7 +1590,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1605,7 +1608,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1622,7 +1626,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1654,7 +1659,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string} url HTTP url string that supports colon param matching.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled. See #when for more info.
|
||||
* order to change how a matched request is handled.
|
||||
* See {@link ngMock.$httpBackend#when `when`} for more info.
|
||||
*/
|
||||
$httpBackend.whenRoute = function(method, url) {
|
||||
var pathObj = parseRoute(url);
|
||||
@@ -1743,8 +1749,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1758,8 +1765,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1773,8 +1781,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1788,11 +1797,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1806,11 +1816,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1824,11 +1835,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1842,7 +1854,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)=} url HTTP url or function that receives an url
|
||||
* and returns true if the url matches the current definition.
|
||||
* and returns true if the url matches the current expectation.
|
||||
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1860,7 +1872,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string} url HTTP url string that supports colon param matching.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled. See #expect for more info.
|
||||
* order to change how a matched request is handled.
|
||||
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
|
||||
*/
|
||||
$httpBackend.expectRoute = function(method, url) {
|
||||
var pathObj = parseRoute(url);
|
||||
@@ -2332,14 +2345,11 @@ angular.mock.$RootElementProvider = function() {
|
||||
*/
|
||||
function createControllerDecorator() {
|
||||
angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
|
||||
return function(expression, locals, later, ident) {
|
||||
if (later && typeof later === 'object') {
|
||||
var instantiate = $delegate(expression, locals, true, ident);
|
||||
var instance = instantiate();
|
||||
angular.extend(instance, later);
|
||||
return instance;
|
||||
}
|
||||
return $delegate(expression, locals, later, ident);
|
||||
return function(expression, locals, bindings, ident) {
|
||||
if (angular.isString(bindings)) ident = bindings;
|
||||
var instance = $delegate(expression, locals, ident);
|
||||
angular.extend(instance, bindings);
|
||||
return instance;
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -2756,6 +2766,39 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#matchLatestDefinition
|
||||
* @module ngMockE2E
|
||||
* @description
|
||||
* This method can be used to change which mocked responses `$httpBackend` returns, when defining
|
||||
* them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
|
||||
* By default, `$httpBackend` returns the first definition that matches. When setting
|
||||
* `$http.matchLatestDefinition(true)`, it will use the last response that matches, i.e. the
|
||||
* one that was added last.
|
||||
*
|
||||
* ```js
|
||||
* hb.when('GET', '/url1').respond(200, 'content', {});
|
||||
* hb.when('GET', '/url1').respond(201, 'another', {});
|
||||
* hb('GET', '/url1'); // receives "content"
|
||||
*
|
||||
* $http.matchLatestDefinition(true)
|
||||
* hb('GET', '/url1'); // receives "another"
|
||||
*
|
||||
* hb.when('GET', '/url1').respond(201, 'onemore', {});
|
||||
* hb('GET', '/url1'); // receives "onemore"
|
||||
* ```
|
||||
*
|
||||
* This is useful if a you have a default response that is overriden inside specific tests.
|
||||
*
|
||||
* Note that different from config methods on providers, `matchLatestDefinition()` can be changed
|
||||
* even when the application is already running.
|
||||
*
|
||||
* @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
|
||||
* If omitted, it will return the current value.
|
||||
* @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
|
||||
* as a getter
|
||||
*/
|
||||
angular.mock.e2e = {};
|
||||
angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
+43
-7
@@ -183,11 +183,22 @@ function $RouteProvider() {
|
||||
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
|
||||
* route definition, will cause the latter to be ignored.
|
||||
*
|
||||
* - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
|
||||
* (inluding the path) even if the new URL maps to the same route.
|
||||
*
|
||||
* If the option is set to `false` and the URL in the browser changes, but the new URL maps
|
||||
* to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without
|
||||
* reloading the route).
|
||||
*
|
||||
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
|
||||
* or `$location.hash()` changes.
|
||||
*
|
||||
* If the option is set to `false` and url in the browser changes, then
|
||||
* `$routeUpdate` event is broadcasted on the root scope.
|
||||
* If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
|
||||
* event is broadcasted on the root scope (without reloading the route).
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** This option has no effect if `reloadOnUrl` is set to `false`.
|
||||
* </div>
|
||||
*
|
||||
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
|
||||
*
|
||||
@@ -202,6 +213,9 @@ function $RouteProvider() {
|
||||
this.when = function(path, route) {
|
||||
//copy original route object to preserve params inherited from proto chain
|
||||
var routeCopy = shallowCopy(route);
|
||||
if (angular.isUndefined(routeCopy.reloadOnUrl)) {
|
||||
routeCopy.reloadOnUrl = true;
|
||||
}
|
||||
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
|
||||
routeCopy.reloadOnSearch = true;
|
||||
}
|
||||
@@ -544,8 +558,9 @@ function $RouteProvider() {
|
||||
* @name $route#$routeUpdate
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
||||
* instance of the Controller.
|
||||
* Broadcasted if the same instance of a route (including template, controller instance,
|
||||
* resolved dependencies, etc.) is being reused. This can happen if either `reloadOnSearch` or
|
||||
* `reloadOnUrl` has been set to `false`.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object
|
||||
* @param {Route} current Current/previous route information.
|
||||
@@ -653,9 +668,7 @@ function $RouteProvider() {
|
||||
var lastRoute = $route.current;
|
||||
|
||||
preparedRoute = parseRoute();
|
||||
preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
|
||||
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
|
||||
&& !preparedRoute.reloadOnSearch && !forceReload;
|
||||
preparedRouteIsUpdateOnly = isNavigationUpdateOnly(preparedRoute, lastRoute);
|
||||
|
||||
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
|
||||
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
|
||||
@@ -835,6 +848,29 @@ function $RouteProvider() {
|
||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} newRoute - The new route configuration (as returned by `parseRoute()`).
|
||||
* @param {Object} oldRoute - The previous route configuration (as returned by `parseRoute()`).
|
||||
* @returns {boolean} Whether this is an "update-only" navigation, i.e. the URL maps to the same
|
||||
* route and it can be reused (based on the config and the type of change).
|
||||
*/
|
||||
function isNavigationUpdateOnly(newRoute, oldRoute) {
|
||||
// IF this is not a forced reload
|
||||
return !forceReload
|
||||
// AND both `newRoute`/`oldRoute` are defined
|
||||
&& newRoute && oldRoute
|
||||
// AND they map to the same Route Definition Object
|
||||
&& (newRoute.$$route === oldRoute.$$route)
|
||||
// AND `reloadOnUrl` is disabled
|
||||
&& (!newRoute.reloadOnUrl
|
||||
// OR `reloadOnSearch` is disabled
|
||||
|| (!newRoute.reloadOnSearch
|
||||
// AND both routes have the same path params
|
||||
&& angular.equals(newRoute.pathParams, oldRoute.pathParams)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} interpolation of the redirect path with the parameters
|
||||
*/
|
||||
|
||||
@@ -482,25 +482,6 @@ describe('injector', function() {
|
||||
expect(instance).toEqual(new Clazz('a-value'));
|
||||
expect(instance.aVal()).toEqual('a-value');
|
||||
});
|
||||
|
||||
they('should detect ES6 classes regardless of whitespace/comments ($prop)', [
|
||||
'class Test {}',
|
||||
'class Test{}',
|
||||
'class //<--ES6 stuff\nTest {}',
|
||||
'class//<--ES6 stuff\nTest {}',
|
||||
'class {}',
|
||||
'class{}',
|
||||
'class //<--ES6 stuff\n {}',
|
||||
'class//<--ES6 stuff\n {}',
|
||||
'class/* Test */{}',
|
||||
'class /* Test */ {}'
|
||||
], function(classDefinition) {
|
||||
// eslint-disable-next-line no-eval
|
||||
var Clazz = eval('(' + classDefinition + ')');
|
||||
var instance = injector.invoke(Clazz);
|
||||
|
||||
expect(instance).toEqual(jasmine.any(Clazz));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ beforeEach(function() {
|
||||
|
||||
function generateCompare(isNot) {
|
||||
return function(actual, namespace, code, content) {
|
||||
|
||||
var matcher = new MinErrMatcher(isNot, namespace, code, content, {
|
||||
inputType: 'error',
|
||||
expectedAction: 'equal',
|
||||
|
||||
+49
-17
@@ -2,32 +2,57 @@
|
||||
|
||||
describe('errors', function() {
|
||||
var originalObjectMaxDepthInErrorMessage = minErrConfig.objectMaxDepth;
|
||||
var originalUrlErrorParamsEnabled = minErrConfig.urlErrorParamsEnabled;
|
||||
|
||||
afterEach(function() {
|
||||
minErrConfig.objectMaxDepth = originalObjectMaxDepthInErrorMessage;
|
||||
minErrConfig.urlErrorParamsEnabled = originalUrlErrorParamsEnabled;
|
||||
});
|
||||
|
||||
describe('errorHandlingConfig', function() {
|
||||
it('should get default objectMaxDepth', function() {
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(5);
|
||||
describe('objectMaxDepth',function() {
|
||||
it('should get default objectMaxDepth', function() {
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(5);
|
||||
});
|
||||
|
||||
it('should set objectMaxDepth', function() {
|
||||
errorHandlingConfig({objectMaxDepth: 3});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(3);
|
||||
});
|
||||
|
||||
it('should not change objectMaxDepth when undefined is supplied', function() {
|
||||
errorHandlingConfig({objectMaxDepth: undefined});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(originalObjectMaxDepthInErrorMessage);
|
||||
});
|
||||
|
||||
they('should set objectMaxDepth to NaN when $prop is supplied',
|
||||
[NaN, null, true, false, -1, 0], function(maxDepth) {
|
||||
errorHandlingConfig({objectMaxDepth: maxDepth});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBeNaN();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should set objectMaxDepth', function() {
|
||||
errorHandlingConfig({objectMaxDepth: 3});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(3);
|
||||
|
||||
describe('urlErrorParamsEnabled',function() {
|
||||
|
||||
it('should get default urlErrorParamsEnabled', function() {
|
||||
expect(errorHandlingConfig().urlErrorParamsEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should set urlErrorParamsEnabled', function() {
|
||||
errorHandlingConfig({urlErrorParamsEnabled: false});
|
||||
expect(errorHandlingConfig().urlErrorParamsEnabled).toBe(false);
|
||||
errorHandlingConfig({urlErrorParamsEnabled: true});
|
||||
expect(errorHandlingConfig().urlErrorParamsEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should not change its value when non-boolean is supplied', function() {
|
||||
errorHandlingConfig({urlErrorParamsEnabled: 123});
|
||||
expect(errorHandlingConfig().urlErrorParamsEnabled).toBe(originalUrlErrorParamsEnabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change objectMaxDepth when undefined is supplied', function() {
|
||||
errorHandlingConfig({objectMaxDepth: undefined});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBe(originalObjectMaxDepthInErrorMessage);
|
||||
});
|
||||
|
||||
they('should set objectMaxDepth to NaN when $prop is supplied',
|
||||
[NaN, null, true, false, -1, 0], function(maxDepth) {
|
||||
errorHandlingConfig({objectMaxDepth: maxDepth});
|
||||
expect(errorHandlingConfig().objectMaxDepth).toBeNaN();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('minErr', function() {
|
||||
@@ -165,7 +190,6 @@ describe('errors', function() {
|
||||
.toMatch(/^[\s\S]*\?p0=a&p1=b&p2=value%20with%20space$/);
|
||||
});
|
||||
|
||||
|
||||
it('should strip error reference urls from the error message parameters', function() {
|
||||
var firstError = testError('firstcode', 'longer string and so on');
|
||||
|
||||
@@ -177,5 +201,13 @@ describe('errors', function() {
|
||||
'%3A%2F%2Ferrors.angularjs.org%2F%22NG_VERSION_FULL%22%2Ftest%2Ffirstcode');
|
||||
});
|
||||
|
||||
it('should not generate URL query parameters when urlErrorParamsEnabled is false', function() {
|
||||
|
||||
errorHandlingConfig({urlErrorParamsEnabled: false});
|
||||
|
||||
expect(testError('acode', 'aproblem', 'a', 'b', 'c').message).toBe('[test:acode] aproblem\n' +
|
||||
'https://errors.angularjs.org/"NG_VERSION_FULL"/test/acode');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8843,6 +8843,50 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should correctly handle multi-element directives', function() {
|
||||
module(function() {
|
||||
directive('foo', valueFn({
|
||||
template: '[<div ng-transclude></div>]',
|
||||
transclude: true
|
||||
}));
|
||||
directive('bar', valueFn({
|
||||
template: '[<div ng-transclude="header"></div>|<div ng-transclude="footer"></div>]',
|
||||
transclude: {
|
||||
header: 'header',
|
||||
footer: 'footer'
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var tmplWithFoo =
|
||||
'<foo>' +
|
||||
'<div ng-if-start="true">Hello, </div>' +
|
||||
'<div ng-if-end>world!</div>' +
|
||||
'</foo>';
|
||||
var tmplWithBar =
|
||||
'<bar>' +
|
||||
'<header ng-if-start="true">This is a </header>' +
|
||||
'<header ng-if-end>header!</header>' +
|
||||
'<footer ng-if-start="true">This is a </footer>' +
|
||||
'<footer ng-if-end>footer!</footer>' +
|
||||
'</bar>';
|
||||
|
||||
var elem1 = $compile(tmplWithFoo)($rootScope);
|
||||
var elem2 = $compile(tmplWithBar)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elem1.text()).toBe('[Hello, world!]');
|
||||
expect(elem2.text()).toBe('[This is a header!|This is a footer!]');
|
||||
|
||||
dealoc(elem1);
|
||||
dealoc(elem2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//see issue https://github.com/angular/angular.js/issues/12936
|
||||
it('should use the proper scope when it is on the root element of a replaced directive template', function() {
|
||||
module(function() {
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('ngModel', function() {
|
||||
|
||||
describe('NgModelController', function() {
|
||||
/* global NgModelController: false */
|
||||
var ctrl, scope, ngModelAccessor, element, parentFormCtrl;
|
||||
var ctrl, scope, element, parentFormCtrl;
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
var attrs = {name: 'testAlias', ngModel: 'value'};
|
||||
@@ -21,7 +21,6 @@ describe('ngModel', function() {
|
||||
element = jqLite('<form><input></form>');
|
||||
|
||||
scope = $rootScope;
|
||||
ngModelAccessor = jasmine.createSpy('ngModel accessor');
|
||||
ctrl = $controller(NgModelController, {
|
||||
$scope: scope,
|
||||
$element: element.find('input'),
|
||||
@@ -438,6 +437,13 @@ describe('ngModel', function() {
|
||||
expect(ctrl.$modelValue).toBe('c');
|
||||
expect(scope.value).toBe('c');
|
||||
}));
|
||||
|
||||
|
||||
it('should not throw an error if the scope has been destroyed', function() {
|
||||
scope.$destroy();
|
||||
ctrl.$setViewValue('some-val');
|
||||
expect(ctrl.$viewValue).toBe('some-val');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngRef', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
toEqualJq: function(util) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
// Jquery <= 2.2 objects add a context property that is irrelevant for equality
|
||||
if (actual && actual.hasOwnProperty('context')) {
|
||||
delete actual.context;
|
||||
}
|
||||
|
||||
if (expected && expected.hasOwnProperty('context')) {
|
||||
delete expected.context;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: util.equals(actual, expected)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('on a component', function() {
|
||||
|
||||
var myComponentController, attributeDirectiveController, $rootScope, $compile;
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'foo',
|
||||
controller: function() {
|
||||
myComponentController = this;
|
||||
}
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {
|
||||
attributeDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
it('should bind in the current scope the controller of a component', function() {
|
||||
$rootScope.$ctrl = 'undamaged';
|
||||
|
||||
$compile('<my-component ng-ref="myComponentRef"></my-component>')($rootScope);
|
||||
expect($rootScope.$ctrl).toBe('undamaged');
|
||||
expect($rootScope.myComponentRef).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should throw if the expression is not assignable', function() {
|
||||
expect(function() {
|
||||
$compile('<my-component ng-ref="\'hello\'"></my-component>')($rootScope);
|
||||
}).toThrowMinErr('ngRef', 'nonassign', 'Expression in ngRef="\'hello\'" is non-assignable!');
|
||||
});
|
||||
|
||||
it('should work with non:normalized entity name', function() {
|
||||
$compile('<my:component ng-ref="myComponent1"></my:component>')($rootScope);
|
||||
expect($rootScope.myComponent1).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with data-non-normalized entity name', function() {
|
||||
$compile('<data-my-component ng-ref="myComponent2"></data-my-component>')($rootScope);
|
||||
expect($rootScope.myComponent2).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with x-non-normalized entity name', function() {
|
||||
$compile('<x-my-component ng-ref="myComponent3"></x-my-component>')($rootScope);
|
||||
expect($rootScope.myComponent3).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with data-non-normalized attribute name', function() {
|
||||
$compile('<my-component data-ng-ref="myComponent1"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponent1).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with x-non-normalized attribute name', function() {
|
||||
$compile('<my-component x-ng-ref="myComponent2"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponent2).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should not bind the controller of an attribute directive', function() {
|
||||
$compile('<my-component attribute-directive-1 ng-ref="myComponentRef"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponentRef).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should not leak to parent scopes', function() {
|
||||
var template =
|
||||
'<div ng-if="true">' +
|
||||
'<my-component ng-ref="myComponent"></my-component>' +
|
||||
'</div>';
|
||||
$compile(template)($rootScope);
|
||||
expect($rootScope.myComponent).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should nullify the variable once the component is destroyed', function() {
|
||||
var template = '<div><my-component ng-ref="myComponent"></my-component></div>';
|
||||
|
||||
var element = $compile(template)($rootScope);
|
||||
expect($rootScope.myComponent).toBe(myComponentController);
|
||||
|
||||
var componentElement = element.children();
|
||||
var isolateScope = componentElement.isolateScope();
|
||||
componentElement.remove();
|
||||
isolateScope.$destroy();
|
||||
expect($rootScope.myComponent).toBe(null);
|
||||
});
|
||||
|
||||
it('should be compatible with entering/leaving components', inject(function($animate) {
|
||||
var template = '<my-component ng-ref="myComponent"></my-component>';
|
||||
$rootScope.$ctrl = {};
|
||||
var parent = $compile('<div></div>')($rootScope);
|
||||
|
||||
var leaving = $compile(template)($rootScope);
|
||||
var leavingController = myComponentController;
|
||||
|
||||
$animate.enter(leaving, parent);
|
||||
expect($rootScope.myComponent).toBe(leavingController);
|
||||
|
||||
var entering = $compile(template)($rootScope);
|
||||
var enteringController = myComponentController;
|
||||
|
||||
$animate.enter(entering, parent);
|
||||
$animate.leave(leaving, parent);
|
||||
expect($rootScope.myComponent).toBe(enteringController);
|
||||
}));
|
||||
|
||||
it('should allow binding to a nested property', function() {
|
||||
$rootScope.obj = {};
|
||||
|
||||
$compile('<my-component ng-ref="obj.myComponent"></my-component>')($rootScope);
|
||||
expect($rootScope.obj.myComponent).toBe(myComponentController);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should bind the jqlite wrapped DOM element if there is no component', inject(function($compile, $rootScope) {
|
||||
|
||||
var el = $compile('<span ng-ref="mySpan">my text</span>')($rootScope);
|
||||
|
||||
expect($rootScope.mySpan).toEqualJq(el);
|
||||
expect($rootScope.mySpan[0].textContent).toBe('my text');
|
||||
}));
|
||||
|
||||
it('should nullify the expression value if the DOM element is destroyed', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div><span ng-ref="mySpan">my text</span></div>')($rootScope);
|
||||
element.children().remove();
|
||||
expect($rootScope.mySpan).toBe(null);
|
||||
}));
|
||||
|
||||
it('should bind the controller of an element directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
$compile('<my-directive ng-ref="myDirective"></my-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myDirective).toBe(myDirectiveController);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngRefRead', function() {
|
||||
|
||||
it('should bind the element instead of the controller of a component if ngRefRead="$element" is set', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
var el = $compile('<my-component ng-ref="myEl" ng-ref-read="$element"></my-component>')($rootScope);
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind the element instead an element-directive controller if ngRefRead="$element" is set', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<my-directive ng-ref="myEl" ng-ref-read="$element"></my-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind an attribute-directive controller if ngRefRead="controllerName" is set', function() {
|
||||
var attrDirective1Controller;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('elementDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective1', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {
|
||||
attrDirective1Controller = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective2', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<element-directive' +
|
||||
'attribute-directive-1' +
|
||||
'attribute-directive-2' +
|
||||
'ng-ref="myController"' +
|
||||
'ng-ref-read="$element"></element-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myController).toBe(attrDirective1Controller);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if no controller is found for the ngRefRead value', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('elementDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
expect(function() {
|
||||
$compile('<element-directive ' +
|
||||
'ng-ref="myController"' +
|
||||
'ng-ref-read="attribute"></element-directive>')($rootScope);
|
||||
}).toThrowMinErr('ngRef', 'noctrl', 'The controller for ngRefRead="attribute" could not be found on ngRef="myController"');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should bind the jqlite element if the controller is on an attribute-directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: 'my text',
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<div my-directive ng-ref="myEl"></div>')($rootScope);
|
||||
|
||||
expect(myDirectiveController).toBeDefined();
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind the jqlite element if the controller is on an class-directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
template: 'my text',
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<div class="my-directive" ng-ref="myEl"></div>')($rootScope);
|
||||
|
||||
expect(myDirectiveController).toBeDefined();
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('transclusion', function() {
|
||||
|
||||
it('should work with simple transclusion', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myComponent', {
|
||||
transclude: true,
|
||||
template: '<ng-transclude></ng-transclude>',
|
||||
controller: function() {
|
||||
this.text = 'SUCCESS';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template = '<my-component ng-ref="myComponent">{{myComponent.text}}</my-component>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude components', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myComponent', {
|
||||
transclude: 'element',
|
||||
controller: function($animate, $element, $transclude) {
|
||||
this.text = 'SUCCESS';
|
||||
this.$postLink = function() {
|
||||
$transclude(function(clone, newScope) {
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-component ng-ref="myComponent">' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with ngIf and transclusion on same element', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: '<ng-transclude></ng-transclude>',
|
||||
transclude: true,
|
||||
controller: function($scope) {
|
||||
this.text = 'SUCCESS';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-component ng-if="present" ng-ref="myComponent" >' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
|
||||
$rootScope.$apply('present = false');
|
||||
expect(element.text()).toBe('');
|
||||
$rootScope.$apply('present = true');
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
$rootScope.$apply('present = false');
|
||||
expect(element.text()).toBe('');
|
||||
$rootScope.$apply('present = true');
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude & destroy components', function() {
|
||||
var myComponentController;
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myTranscludingComponent', {
|
||||
transclude: 'element',
|
||||
controller: function($animate, $element, $transclude) {
|
||||
myComponentController = this;
|
||||
|
||||
var currentClone, currentScope;
|
||||
this.transclude = function(text) {
|
||||
this.text = text;
|
||||
$transclude(function(clone, newScope) {
|
||||
currentClone = clone;
|
||||
currentScope = newScope;
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
};
|
||||
this.destroy = function() {
|
||||
currentClone.remove();
|
||||
currentScope.$destroy();
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-transcluding-component ng-ref="myComponent">' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-transcluding-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
myComponentController.transclude('transcludedOk');
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('transcludedOk');
|
||||
|
||||
myComponentController.destroy();
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude directives', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.directive('myDirective', function($animate) {
|
||||
return {
|
||||
transclude: 'element',
|
||||
controller: function() {
|
||||
this.text = 'SUCCESS';
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl, $transclude) {
|
||||
$transclude(function(clone, newScope) {
|
||||
$animate.enter(clone, element.parent(), element);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-directive ng-ref="myDirective">' +
|
||||
'{{myDirective.text}}' +
|
||||
'</my-directive>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should work with components with templates via $http', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('httpComponent', {
|
||||
templateUrl: 'template.html',
|
||||
controller: function() {
|
||||
this.me = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $httpBackend, $rootScope) {
|
||||
var template = '<div><http-component ng-ref="controller"></http-component></div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$httpBackend.expect('GET', 'template.html').respond('ok');
|
||||
$rootScope.$apply();
|
||||
expect($rootScope.controller).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
expect($rootScope.controller.me).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should work with ngRepeat-ed components', function() {
|
||||
var controllers = [];
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'foo',
|
||||
controller: function() {
|
||||
controllers.push(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
$rootScope.elements = [0,1,2,3,4];
|
||||
$rootScope.controllers = []; // Initialize the array because ngRepeat creates a child scope
|
||||
|
||||
var template = '<div><my-component ng-repeat="(key, el) in elements" ng-ref="controllers[key]"></my-component></div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.controllers).toEqual(controllers);
|
||||
|
||||
$rootScope.$apply('elements = []');
|
||||
|
||||
expect($rootScope.controllers).toEqual([null, null, null, null, null]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+254
-23
@@ -9,17 +9,236 @@ describe('$aria', function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
function injectScopeAndCompiler() {
|
||||
return inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
scope = _$rootScope_;
|
||||
});
|
||||
}
|
||||
describe('with `ngAriaDisable`', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
toHaveAttribute: function toHaveAttributeMatcher() {
|
||||
return {
|
||||
compare: function toHaveAttributeCompare(element, attr) {
|
||||
var node = element[0];
|
||||
var pass = node.hasAttribute(attr);
|
||||
var message = 'Expected `' + node.outerHTML + '` ' + (pass ? 'not ' : '') +
|
||||
'to have attribute `' + attr + '`.';
|
||||
|
||||
function compileElement(inputHtml) {
|
||||
element = $compile(inputHtml)(scope);
|
||||
scope.$digest();
|
||||
}
|
||||
return {
|
||||
pass: pass,
|
||||
message: message
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ariaChecked
|
||||
it('should not attach aria-checked to custom checkbox', function() {
|
||||
compileElement('<div role="checkbox" ng-model="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-checked');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-checked');
|
||||
});
|
||||
|
||||
it('should not attach aria-checked to custom radio controls', function() {
|
||||
compileElement(
|
||||
'<div role="radio" ng-model="val" value="one" ng-aria-disable></div>' +
|
||||
'<div role="radio" ng-model="val" value="two" ng-aria-disable></div>');
|
||||
|
||||
var radio1 = element.eq(0);
|
||||
var radio2 = element.eq(1);
|
||||
|
||||
scope.$apply('val = "one"');
|
||||
expect(radio1).not.toHaveAttribute('aria-checked');
|
||||
expect(radio2).not.toHaveAttribute('aria-checked');
|
||||
|
||||
scope.$apply('val = "two"');
|
||||
expect(radio1).not.toHaveAttribute('aria-checked');
|
||||
expect(radio2).not.toHaveAttribute('aria-checked');
|
||||
});
|
||||
|
||||
// ariaDisabled
|
||||
it('should not attach aria-disabled to custom controls', function() {
|
||||
compileElement('<div ng-disabled="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-disabled');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-disabled');
|
||||
});
|
||||
|
||||
// ariaHidden
|
||||
it('should not attach aria-hidden to `ngShow`', function() {
|
||||
compileElement('<div ng-show="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-hidden');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
|
||||
it('should not attach aria-hidden to `ngHide`', function() {
|
||||
compileElement('<div ng-hide="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-hidden');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
|
||||
// ariaInvalid
|
||||
it('should not attach aria-invalid to input', function() {
|
||||
compileElement('<input ng-model="val" ng-minlength="10" ng-aria-disable />');
|
||||
|
||||
scope.$apply('val = "lt 10"');
|
||||
expect(element).not.toHaveAttribute('aria-invalid');
|
||||
|
||||
scope.$apply('val = "gt 10 characters"');
|
||||
expect(element).not.toHaveAttribute('aria-invalid');
|
||||
});
|
||||
|
||||
it('should not attach aria-invalid to custom controls', function() {
|
||||
compileElement('<div role="textbox" ng-model="val" ng-minlength="10" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = "lt 10"');
|
||||
expect(element).not.toHaveAttribute('aria-invalid');
|
||||
|
||||
scope.$apply('val = "gt 10 characters"');
|
||||
expect(element).not.toHaveAttribute('aria-invalid');
|
||||
});
|
||||
|
||||
// ariaLive
|
||||
it('should not attach aria-live to `ngMessages`', function() {
|
||||
compileElement('<div ng-messages="val" ng-aria-disable>');
|
||||
expect(element).not.toHaveAttribute('aria-live');
|
||||
});
|
||||
|
||||
// ariaReadonly
|
||||
it('should not attach aria-readonly to custom controls', function() {
|
||||
compileElement('<div ng-readonly="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-readonly');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-readonly');
|
||||
});
|
||||
|
||||
// ariaRequired
|
||||
it('should not attach aria-required to custom controls with `required`', function() {
|
||||
compileElement('<div ng-model="val" required ng-aria-disable></div>');
|
||||
expect(element).not.toHaveAttribute('aria-required');
|
||||
});
|
||||
|
||||
it('should not attach aria-required to custom controls with `ngRequired`', function() {
|
||||
compileElement('<div ng-model="val" ng-required="val" ng-aria-disable></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element).not.toHaveAttribute('aria-required');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element).not.toHaveAttribute('aria-required');
|
||||
});
|
||||
|
||||
// ariaValue
|
||||
it('should not attach aria-value* to input[range]', function() {
|
||||
compileElement('<input type="range" ng-model="val" min="0" max="100" ng-aria-disable />');
|
||||
|
||||
expect(element).not.toHaveAttribute('aria-valuemax');
|
||||
expect(element).not.toHaveAttribute('aria-valuemin');
|
||||
expect(element).not.toHaveAttribute('aria-valuenow');
|
||||
|
||||
scope.$apply('val = 50');
|
||||
expect(element).not.toHaveAttribute('aria-valuemax');
|
||||
expect(element).not.toHaveAttribute('aria-valuemin');
|
||||
expect(element).not.toHaveAttribute('aria-valuenow');
|
||||
|
||||
scope.$apply('val = 150');
|
||||
expect(element).not.toHaveAttribute('aria-valuemax');
|
||||
expect(element).not.toHaveAttribute('aria-valuemin');
|
||||
expect(element).not.toHaveAttribute('aria-valuenow');
|
||||
});
|
||||
|
||||
it('should not attach aria-value* to custom controls', function() {
|
||||
compileElement(
|
||||
'<div role="progressbar" ng-model="val" min="0" max="100" ng-aria-disable></div>' +
|
||||
'<div role="slider" ng-model="val" min="0" max="100" ng-aria-disable></div>');
|
||||
|
||||
var progressbar = element.eq(0);
|
||||
var slider = element.eq(1);
|
||||
|
||||
['aria-valuemax', 'aria-valuemin', 'aria-valuenow'].forEach(function(attr) {
|
||||
expect(progressbar).not.toHaveAttribute(attr);
|
||||
expect(slider).not.toHaveAttribute(attr);
|
||||
});
|
||||
|
||||
scope.$apply('val = 50');
|
||||
['aria-valuemax', 'aria-valuemin', 'aria-valuenow'].forEach(function(attr) {
|
||||
expect(progressbar).not.toHaveAttribute(attr);
|
||||
expect(slider).not.toHaveAttribute(attr);
|
||||
});
|
||||
|
||||
scope.$apply('val = 150');
|
||||
['aria-valuemax', 'aria-valuemin', 'aria-valuenow'].forEach(function(attr) {
|
||||
expect(progressbar).not.toHaveAttribute(attr);
|
||||
expect(slider).not.toHaveAttribute(attr);
|
||||
});
|
||||
});
|
||||
|
||||
// bindKeypress
|
||||
it('should not bind keypress to `ngClick`', function() {
|
||||
scope.onClick = jasmine.createSpy('onClick');
|
||||
compileElement(
|
||||
'<div ng-click="onClick()" tabindex="0" ng-aria-disable></div>' +
|
||||
'<ul><li ng-click="onClick()" tabindex="0" ng-aria-disable></li></ul>');
|
||||
|
||||
var div = element.find('div');
|
||||
var li = element.find('li');
|
||||
|
||||
div.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
li.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
expect(scope.onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// bindRoleForClick
|
||||
it('should not attach role to custom controls', function() {
|
||||
compileElement(
|
||||
'<div ng-click="onClick()" ng-aria-disable></div>' +
|
||||
'<div type="checkbox" ng-model="val" ng-aria-disable></div>' +
|
||||
'<div type="radio" ng-model="val" ng-aria-disable></div>' +
|
||||
'<div type="range" ng-model="val" ng-aria-disable></div>');
|
||||
|
||||
expect(element.eq(0)).not.toHaveAttribute('role');
|
||||
expect(element.eq(1)).not.toHaveAttribute('role');
|
||||
expect(element.eq(2)).not.toHaveAttribute('role');
|
||||
expect(element.eq(3)).not.toHaveAttribute('role');
|
||||
});
|
||||
|
||||
// tabindex
|
||||
it('should not attach tabindex to custom controls', function() {
|
||||
compileElement(
|
||||
'<div role="checkbox" ng-model="val" ng-aria-disable></div>' +
|
||||
'<div role="slider" ng-model="val" ng-aria-disable></div>');
|
||||
|
||||
expect(element.eq(0)).not.toHaveAttribute('tabindex');
|
||||
expect(element.eq(1)).not.toHaveAttribute('tabindex');
|
||||
});
|
||||
|
||||
it('should not attach tabindex to `ngClick` or `ngDblclick`', function() {
|
||||
compileElement(
|
||||
'<div ng-click="onClick()" ng-aria-disable></div>' +
|
||||
'<div ng-dblclick="onDblclick()" ng-aria-disable></div>');
|
||||
|
||||
expect(element.eq(0)).not.toHaveAttribute('tabindex');
|
||||
expect(element.eq(1)).not.toHaveAttribute('tabindex');
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-hidden', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
@@ -895,19 +1114,31 @@ describe('$aria', function() {
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectAriaAttrOnEachElement(elem, ariaAttr, expected) {
|
||||
angular.forEach(elem, function(val) {
|
||||
expect(angular.element(val).attr(ariaAttr)).toBe(expected);
|
||||
});
|
||||
}
|
||||
// Helpers
|
||||
function compileElement(inputHtml) {
|
||||
element = $compile(inputHtml)(scope);
|
||||
scope.$digest();
|
||||
}
|
||||
|
||||
function configAriaProvider(config) {
|
||||
return function() {
|
||||
angular.module('ariaTest', ['ngAria']).config(function($ariaProvider) {
|
||||
$ariaProvider.config(config);
|
||||
function configAriaProvider(config) {
|
||||
return function() {
|
||||
module(function($ariaProvider) {
|
||||
$ariaProvider.config(config);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function expectAriaAttrOnEachElement(elem, ariaAttr, expected) {
|
||||
angular.forEach(elem, function(val) {
|
||||
expect(angular.element(val).attr(ariaAttr)).toBe(expected);
|
||||
});
|
||||
module('ariaTest');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function injectScopeAndCompiler() {
|
||||
return inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
scope = _$rootScope_;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -661,6 +661,100 @@ describe('ngMessages', function() {
|
||||
);
|
||||
|
||||
|
||||
describe('default message', function() {
|
||||
it('should render a default message when no message matches', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-messages="col">' +
|
||||
' <div ng-message="val">Message is set</div>' +
|
||||
' <div ng-message-default>Default message is set</div>' +
|
||||
'</div>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { unexpected: false };
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.text().trim()).toBe('');
|
||||
expect(element).not.toHaveClass('ng-active');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { unexpected: true };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Default message is set');
|
||||
expect(element).toHaveClass('ng-active');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { unexpected: false };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('');
|
||||
expect(element).not.toHaveClass('ng-active');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { val: true, unexpected: true };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Message is set');
|
||||
expect(element).toHaveClass('ng-active');
|
||||
}));
|
||||
|
||||
it('should not render a default message with ng-messages-multiple if another error matches',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-messages="col" ng-messages-multiple>' +
|
||||
' <div ng-message="val">Message is set</div>' +
|
||||
' <div ng-message="other">Other message is set</div>' +
|
||||
' <div ng-message-default>Default message is set</div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
expect(element.text().trim()).toBe('');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { val: true, other: false, unexpected: false };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Message is set');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { val: true, other: true, unexpected: true };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Message is set Other message is set');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { val: false, other: false, unexpected: true };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Default message is set');
|
||||
})
|
||||
);
|
||||
|
||||
it('should handle a default message with ngIf', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-messages="col">' +
|
||||
' <div ng-message="val">Message is set</div>' +
|
||||
' <div ng-if="default" ng-message-default>Default message is set</div>' +
|
||||
'</div>')($rootScope);
|
||||
$rootScope.default = true;
|
||||
$rootScope.col = {unexpected: true};
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.text().trim()).toBe('Default message is set');
|
||||
|
||||
$rootScope.$apply('default = false');
|
||||
|
||||
expect(element.text().trim()).toBe('');
|
||||
|
||||
$rootScope.$apply('default = true');
|
||||
|
||||
expect(element.text().trim()).toBe('Default message is set');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = { val: true };
|
||||
});
|
||||
|
||||
expect(element.text().trim()).toBe('Message is set');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when including templates', function() {
|
||||
they('should work with a dynamic collection model which is managed by ngRepeat',
|
||||
{'<div ng-messages-include="...">': '<div ng-messages="item">' +
|
||||
|
||||
+389
-57
@@ -65,8 +65,8 @@ describe('$route', function() {
|
||||
$httpBackend.when('GET', 'Chapter.html').respond('chapter');
|
||||
$httpBackend.when('GET', 'test.html').respond('test');
|
||||
$httpBackend.when('GET', 'foo.html').respond('foo');
|
||||
$httpBackend.when('GET', 'baz.html').respond('baz');
|
||||
$httpBackend.when('GET', 'bar.html').respond('bar');
|
||||
$httpBackend.when('GET', 'baz.html').respond('baz');
|
||||
$httpBackend.when('GET', 'http://example.com/trusted-template.html').respond('cross domain trusted template');
|
||||
$httpBackend.when('GET', '404.html').respond('not found');
|
||||
};
|
||||
@@ -76,6 +76,7 @@ describe('$route', function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
|
||||
it('should allow cancellation via $locationChangeStart via $routeChangeStart', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/Edit', {
|
||||
@@ -1677,95 +1678,413 @@ describe('$route', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('reloadOnUrl', function() {
|
||||
it('should reload when `reloadOnUrl` is true and `.url()` changes', function() {
|
||||
var routeChange = jasmine.createSpy('routeChange');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/path/:param', {});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
|
||||
// Initial load
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({param: 'foo'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Reload on `path` change
|
||||
$location.path('/path/bar');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({param: 'bar'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Reload on `search` change
|
||||
$location.search('foo', 'bar');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({param: 'bar', foo: 'bar'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Reload on `hash` change
|
||||
$location.hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({param: 'bar', foo: 'bar'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should reload when `reloadOnUrl` is false and URL maps to different route',
|
||||
function() {
|
||||
var routeChange = jasmine.createSpy('routeChange');
|
||||
var routeUpdate = jasmine.createSpy('routeUpdate');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/path/:param', {reloadOnUrl: false}).
|
||||
otherwise({});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
$rootScope.$on('$routeUpdate', routeUpdate);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
|
||||
// Initial load
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect(routeUpdate).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({param: 'foo'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Route change
|
||||
$location.path('/other/path/bar');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect(routeUpdate).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should not reload when `reloadOnUrl` is false and URL maps to the same route',
|
||||
function() {
|
||||
var routeChange = jasmine.createSpy('routeChange');
|
||||
var routeUpdate = jasmine.createSpy('routeUpdate');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/path/:param', {reloadOnUrl: false});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
$rootScope.$on('$routeUpdate', routeUpdate);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
|
||||
// Initial load
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect(routeUpdate).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({param: 'foo'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Route update (no reload)
|
||||
$location.path('/path/bar').search('foo', 'bar').hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect(routeUpdate).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({param: 'bar', foo: 'bar'});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should update `$routeParams` even when not reloading a route', function() {
|
||||
var routeChange = jasmine.createSpy('routeChange');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/path/:param', {reloadOnUrl: false});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
|
||||
// Initial load
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect($routeParams).toEqual({param: 'foo'});
|
||||
|
||||
routeChange.calls.reset();
|
||||
|
||||
// Route update (no reload)
|
||||
$location.path('/path/bar');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({param: 'bar'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('with `$route.reload()`', function() {
|
||||
var $location;
|
||||
var $log;
|
||||
var $rootScope;
|
||||
var $route;
|
||||
var routeChangeStart;
|
||||
var routeChangeSuccess;
|
||||
|
||||
beforeEach(module(function($routeProvider) {
|
||||
$routeProvider.when('/path/:param', {
|
||||
template: '',
|
||||
reloadOnUrl: false,
|
||||
controller: function Controller($log) {
|
||||
$log.debug('initialized');
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($compile, _$location_, _$log_, _$rootScope_, _$route_) {
|
||||
$location = _$location_;
|
||||
$log = _$log_;
|
||||
$rootScope = _$rootScope_;
|
||||
$route = _$route_;
|
||||
|
||||
routeChangeStart = jasmine.createSpy('routeChangeStart');
|
||||
routeChangeSuccess = jasmine.createSpy('routeChangeSuccess');
|
||||
|
||||
$rootScope.$on('$routeChangeStart', routeChangeStart);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChangeSuccess);
|
||||
|
||||
element = $compile('<div><ng-view></ng-view></div>')($rootScope);
|
||||
}));
|
||||
|
||||
|
||||
it('should reload the current route', function() {
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect($location.path()).toBe('/path/foo');
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).toHaveBeenCalledOnce();
|
||||
expect($log.debug.logs).toEqual([['initialized']]);
|
||||
|
||||
routeChangeStart.calls.reset();
|
||||
routeChangeSuccess.calls.reset();
|
||||
$log.reset();
|
||||
|
||||
$route.reload();
|
||||
$rootScope.$digest();
|
||||
expect($location.path()).toBe('/path/foo');
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).toHaveBeenCalledOnce();
|
||||
expect($log.debug.logs).toEqual([['initialized']]);
|
||||
|
||||
$log.reset();
|
||||
});
|
||||
|
||||
|
||||
it('should support preventing a route reload', function() {
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect($location.path()).toBe('/path/foo');
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).toHaveBeenCalledOnce();
|
||||
expect($log.debug.logs).toEqual([['initialized']]);
|
||||
|
||||
routeChangeStart.calls.reset();
|
||||
routeChangeSuccess.calls.reset();
|
||||
$log.reset();
|
||||
|
||||
routeChangeStart.and.callFake(function(evt) { evt.preventDefault(); });
|
||||
|
||||
$route.reload();
|
||||
$rootScope.$digest();
|
||||
expect($location.path()).toBe('/path/foo');
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).not.toHaveBeenCalled();
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should reload the current route even if `reloadOnUrl` is disabled',
|
||||
inject(function($routeParams) {
|
||||
$location.path('/path/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).toHaveBeenCalledOnce();
|
||||
expect($log.debug.logs).toEqual([['initialized']]);
|
||||
expect($routeParams).toEqual({param: 'foo'});
|
||||
|
||||
routeChangeStart.calls.reset();
|
||||
routeChangeSuccess.calls.reset();
|
||||
$log.reset();
|
||||
|
||||
$location.path('/path/bar');
|
||||
$rootScope.$digest();
|
||||
expect(routeChangeStart).not.toHaveBeenCalled();
|
||||
expect(routeChangeSuccess).not.toHaveBeenCalled();
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
expect($routeParams).toEqual({param: 'bar'});
|
||||
|
||||
$route.reload();
|
||||
$rootScope.$digest();
|
||||
expect(routeChangeStart).toHaveBeenCalledOnce();
|
||||
expect(routeChangeSuccess).toHaveBeenCalledOnce();
|
||||
expect($log.debug.logs).toEqual([['initialized']]);
|
||||
expect($routeParams).toEqual({param: 'bar'});
|
||||
|
||||
$log.reset();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadOnSearch', function() {
|
||||
it('should reload a route when reloadOnSearch is enabled and .search() changes', function() {
|
||||
it('should not have any effect if `reloadOnUrl` is false', function() {
|
||||
var reloaded = jasmine.createSpy('route reload');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {controller: angular.noop});
|
||||
$routeProvider.when('/foo', {
|
||||
reloadOnUrl: false,
|
||||
reloadOnSearch: true
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', reloaded);
|
||||
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect(reloaded).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({});
|
||||
|
||||
reloaded.calls.reset();
|
||||
|
||||
// trigger reload
|
||||
// trigger reload (via .search())
|
||||
$location.search({foo: 'bar'});
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({foo:'bar'});
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({foo: 'bar'});
|
||||
|
||||
// trigger reload (via .hash())
|
||||
$location.hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
expect($routeParams).toEqual({foo: 'bar'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not reload a route when reloadOnSearch is disabled and only .search() changes', function() {
|
||||
var routeChange = jasmine.createSpy('route change'),
|
||||
routeUpdate = jasmine.createSpy('route update');
|
||||
it('should reload when `reloadOnSearch` is true and `.search()`/`.hash()` changes',
|
||||
function() {
|
||||
var reloaded = jasmine.createSpy('route reload');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {controller: angular.noop, reloadOnSearch: false});
|
||||
});
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {controller: angular.noop});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
$rootScope.$on('$routeUpdate', routeUpdate);
|
||||
inject(function($route, $location, $rootScope, $routeParams) {
|
||||
$rootScope.$on('$routeChangeStart', reloaded);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({});
|
||||
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalled();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect(routeUpdate).not.toHaveBeenCalled();
|
||||
routeChange.calls.reset();
|
||||
reloaded.calls.reset();
|
||||
|
||||
// don't trigger reload
|
||||
$location.search({foo: 'bar'});
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect(routeUpdate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
// trigger reload (via .search())
|
||||
$location.search({foo: 'bar'});
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({foo: 'bar'});
|
||||
|
||||
reloaded.calls.reset();
|
||||
|
||||
// trigger reload (via .hash())
|
||||
$location.hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(reloaded).toHaveBeenCalledOnce();
|
||||
expect($routeParams).toEqual({foo: 'bar'});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should reload reloadOnSearch route when url differs only in route path param', function() {
|
||||
var routeChange = jasmine.createSpy('route change');
|
||||
it('should not reload when `reloadOnSearch` is false and `.search()`/`.hash()` changes',
|
||||
function() {
|
||||
var routeChange = jasmine.createSpy('route change'),
|
||||
routeUpdate = jasmine.createSpy('route update');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo/:fooId', {controller: angular.noop, reloadOnSearch: false});
|
||||
});
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {controller: angular.noop, reloadOnSearch: false});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
$rootScope.$on('$routeUpdate', routeUpdate);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
|
||||
$location.path('/foo/aaa');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalled();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
routeChange.calls.reset();
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
expect(routeUpdate).not.toHaveBeenCalled();
|
||||
|
||||
$location.path('/foo/bbb');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalled();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
routeChange.calls.reset();
|
||||
routeChange.calls.reset();
|
||||
|
||||
$location.search({foo: 'bar'});
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
// don't trigger reload (via .search())
|
||||
$location.search({foo: 'bar'});
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect(routeUpdate).toHaveBeenCalledOnce();
|
||||
|
||||
routeUpdate.calls.reset();
|
||||
|
||||
// don't trigger reload (via .hash())
|
||||
$location.hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
expect(routeUpdate).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should update params when reloadOnSearch is disabled and .search() changes', function() {
|
||||
it('should reload when `reloadOnSearch` is false and url differs only in route path param',
|
||||
function() {
|
||||
var routeChange = jasmine.createSpy('route change');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo/:fooId', {controller: angular.noop, reloadOnSearch: false});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', routeChange);
|
||||
$rootScope.$on('$routeChangeSuccess', routeChange);
|
||||
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
|
||||
$location.path('/foo/aaa');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
routeChange.calls.reset();
|
||||
|
||||
$location.path('/foo/bbb');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).toHaveBeenCalledTimes(2);
|
||||
routeChange.calls.reset();
|
||||
|
||||
$location.search({foo: 'bar'}).hash('baz');
|
||||
$rootScope.$digest();
|
||||
expect(routeChange).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should update params when `reloadOnSearch` is false and `.search()` changes', function() {
|
||||
var routeParamsWatcher = jasmine.createSpy('routeParamsWatcher');
|
||||
|
||||
module(function($routeProvider) {
|
||||
@@ -1852,7 +2171,8 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reload', function() {
|
||||
|
||||
describe('with `$route.reload()`', function() {
|
||||
var $location;
|
||||
var $log;
|
||||
var $rootScope;
|
||||
@@ -1886,6 +2206,7 @@ describe('$route', function() {
|
||||
element = $compile('<div><div ng-view></div></div>')($rootScope);
|
||||
}));
|
||||
|
||||
|
||||
it('should reload the current route', function() {
|
||||
$location.path('/bar/123');
|
||||
$rootScope.$digest();
|
||||
@@ -1908,6 +2229,7 @@ describe('$route', function() {
|
||||
$log.reset();
|
||||
});
|
||||
|
||||
|
||||
it('should support preventing a route reload', function() {
|
||||
$location.path('/bar/123');
|
||||
$rootScope.$digest();
|
||||
@@ -1930,6 +2252,7 @@ describe('$route', function() {
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should reload even if reloadOnSearch is false', inject(function($routeParams) {
|
||||
$location.path('/bar/123');
|
||||
$rootScope.$digest();
|
||||
@@ -1946,6 +2269,15 @@ describe('$route', function() {
|
||||
expect(routeChangeSuccessSpy).not.toHaveBeenCalled();
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
|
||||
routeChangeSuccessSpy.calls.reset();
|
||||
$log.reset();
|
||||
|
||||
$location.hash('c');
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({barId: '123', a: 'b'});
|
||||
expect(routeChangeSuccessSpy).not.toHaveBeenCalled();
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
|
||||
$route.reload();
|
||||
$rootScope.$digest();
|
||||
expect($routeParams).toEqual({barId: '123', a: 'b'});
|
||||
|
||||
Reference in New Issue
Block a user