Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 197e18d232 | |||
| 7d7b03811d | |||
| bcac64b7b5 | |||
| 8de31388da | |||
| 1d2f5e41b7 | |||
| 6b3da2ddfe | |||
| d556519548 | |||
| 6a7d715944 | |||
| 35f3a4a0e3 | |||
| ff74511291 | |||
| cb04cfcab0 | |||
| 693d133456 | |||
| 996c901ff9 | |||
| 6234cda41c | |||
| f8103a82bf | |||
| 0d764b581d | |||
| 57a37fcc20 | |||
| 7f6ba5534f | |||
| b24bfae585 | |||
| d3e123b0a6 | |||
| bf1acf7b21 | |||
| fdaf4d5e27 | |||
| 1c47abc462 | |||
| 5222703444 | |||
| 4b38b44c91 | |||
| aa28e48e17 | |||
| f31586db41 | |||
| 8337b9b2d4 | |||
| 85a53ea9cd | |||
| 2d87ef8f23 | |||
| 3df8b637f3 | |||
| 28d3126fe5 | |||
| ad21f8feaf | |||
| 7f2df141cd | |||
| 4e735e5363 | |||
| a9db6073c9 | |||
| e48e27aa2b | |||
| 832b383cbc | |||
| c4bff290e5 | |||
| 55b00148dd | |||
| 23550b5e27 | |||
| 97d2a08c5a | |||
| 7f6efb2822 | |||
| b50867001b | |||
| 8b9ce885d3 | |||
| 56dae6fa1b | |||
| 611dcbc035 | |||
| 2c9066e012 | |||
| 8ecc9357ef | |||
| 71dca7c4c2 | |||
| ce77c25b06 | |||
| a1188721e1 | |||
| 1917ff86c4 | |||
| c60e1960c6 | |||
| 268e71eb8b | |||
| 03e6fb3df5 | |||
| 4fc9cf6289 |
+1
-1
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '4.2'
|
||||
- '4.4'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
+220
@@ -1,3 +1,185 @@
|
||||
<a name="1.4.14"></a>
|
||||
# 1.4.14 material-distinction (2016-10-10)
|
||||
|
||||
This release reverts the fix in 1.4.13 and provides an alternative fix that doesn't break Angular Material.
|
||||
|
||||
## Reverts
|
||||
|
||||
- **input:** ensure that hidden input values are correct after history back
|
||||
([ff7451129](https://github.com/angular/angular.js/commit/ff745112915ffa8249520218139b1a8021cd06d0))
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** ensure that hidden input values are correct after history back
|
||||
([35f3a4a0e](https://github.com/angular/angular.js/commit/35f3a4a0e30fef0e019ca0ea2ae434428eaff83b))
|
||||
|
||||
|
||||
<a name="1.2.32"></a>
|
||||
# 1.2.32 alternation-intention (2016-10-10)
|
||||
|
||||
This release reverts the fix in 1.2.31 and provides an alternative fix that doesn't break Angular Material.
|
||||
|
||||
## Reverts
|
||||
|
||||
- **input:** ensure that hidden input values are correct after history back
|
||||
([ed44dd065](https://github.com/angular/angular.js/commit/ed44dd0659f346ced78a112e4a2b30d3af4fd572))
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** ensure that hidden input values are correct after history back
|
||||
([b8a0ecdd6](https://github.com/angular/angular.js/commit/b8a0ecdd6189fb111734eb5b6d4d473d0dcf4c36))
|
||||
|
||||
|
||||
<a name="1.4.13"></a>
|
||||
# 1.4.13 croaking-elderweed (2016-10-10)
|
||||
|
||||
## Bug Fixes
|
||||
- **input:** ensure that hidden input values are correct after history back
|
||||
([693d1334](https://github.com/angular/angular.js/commit/693d1334566f78987f5a361a100db4f889f35abd))
|
||||
|
||||
|
||||
<a name="1.4.12"></a>
|
||||
# 1.4.12
|
||||
|
||||
*Invalid release*
|
||||
|
||||
|
||||
<a name="1.4.11"></a>
|
||||
# 1.4.11 relentless-syncomania (2016-05-27)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** properly handle setting `srcset` to undefined
|
||||
([85a53ea9](https://github.com/angular/angular.js/commit/85a53ea9cd08476496ef680d20f64ae6a7ae7499),
|
||||
[#14470](https://github.com/angular/angular.js/issues/14470), [#14493](https://github.com/angular/angular.js/issues/14493))
|
||||
- **$templateRequest:** trust empty templates in `$templateCache` as well
|
||||
([ad21f8fe](https://github.com/angular/angular.js/commit/ad21f8feaf8659158e8f2092c90eb42c4f13c141),
|
||||
[#14479](https://github.com/angular/angular.js/issues/14479), [#14496](https://github.com/angular/angular.js/issues/14496))
|
||||
- **filters:** always call `splice()` with 2 arguments or more
|
||||
([4e735e53](https://github.com/angular/angular.js/commit/4e735e53638a1c831f5c0455cd123a4127a11505),
|
||||
[#14467](https://github.com/angular/angular.js/issues/14467), [#14489](https://github.com/angular/angular.js/issues/14489))
|
||||
- **formatNumber:** handle small numbers correctly when `gSize` !== `lgSize`
|
||||
([a1188721](https://github.com/angular/angular.js/commit/a1188721e1ac24e1c34e61b4f622f397416ccc7c),
|
||||
[#14289](https://github.com/angular/angular.js/issues/14289), [#14290](https://github.com/angular/angular.js/issues/14290))
|
||||
- **ng-bind-html:** watch the unwrapped value using `$sce.valueOf()` (instead of `toString()`)
|
||||
([f31586db](https://github.com/angular/angular.js/commit/f31586db4115baabaa854c095278f367850a11c5),
|
||||
[#14526](https://github.com/angular/angular.js/issues/14526), [#14527](https://github.com/angular/angular.js/issues/14527))
|
||||
- **ngAnimate:**
|
||||
- safe-guard against missing document
|
||||
([0d764b58](https://github.com/angular/angular.js/commit/0d764b581d8b494190fa280f8870eee8fd039933))
|
||||
- properly handle empty jqLite collections
|
||||
([fdaf4d5e](https://github.com/angular/angular.js/commit/fdaf4d5e27a2ddebf2999db4d35bef7915d32c86),
|
||||
[#14558](https://github.com/angular/angular.js/issues/14558), [#14559](https://github.com/angular/angular.js/issues/14559))
|
||||
- fire callbacks when document is hidden
|
||||
([c4bff290](https://github.com/angular/angular.js/commit/c4bff290e5009e8ae5aa93839ad80a677a9e878f),
|
||||
[#14120](https://github.com/angular/angular.js/issues/14120))
|
||||
- fire callbacks in the correct order for certain skipped animations
|
||||
([23550b5e](https://github.com/angular/angular.js/commit/23550b5e278b4fb3cf1300f1399d8ad6d6771725))
|
||||
- **ngClass:** fix watching of an array expression containing an object
|
||||
([b5086700](https://github.com/angular/angular.js/commit/b50867001bf71998a2f7e381095cb9001166e806),
|
||||
[#14405](https://github.com/angular/angular.js/issues/14405))
|
||||
- **ngMessages:** don't crash when nested messages are removed
|
||||
([71dca7c4](https://github.com/angular/angular.js/commit/71dca7c4c23df95c6c0e5974f0758a3ca34ce819),
|
||||
[#14183](https://github.com/angular/angular.js/issues/14183), [#14242](https://github.com/angular/angular.js/issues/14242))
|
||||
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
|
||||
([56dae6fa](https://github.com/angular/angular.js/commit/56dae6fa1bee88eaffae766a6cdcb8b6c4b89958),
|
||||
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** use createMap() for directive bindings to allow fast forEach
|
||||
([832b383c](https://github.com/angular/angular.js/commit/832b383cbc63c157e62df523d574a75171a8c11c),
|
||||
[#12529](https://github.com/angular/angular.js/issues/12529))
|
||||
|
||||
|
||||
<a name="1.4.10"></a>
|
||||
# 1.4.10 benignant-oscillation (2016-03-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **core:** only call `console.log` when `window.console` exists
|
||||
([beb00e44](https://github.com/angular/angular.js/commit/beb00e44de947981dbe35d5cf7a116e10ea8dc67),
|
||||
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
|
||||
- **$animateCss:** cancel fallback timeout when animation ends normally
|
||||
([a60bbc12](https://github.com/angular/angular.js/commit/a60bbc12e8c5170e70d95f1b2c3e309b3b95cb84),
|
||||
[#13787](https://github.com/angular/angular.js/issues/13787))
|
||||
- **$compile:**
|
||||
- allow directives to have decorators
|
||||
([77cdc37c](https://github.com/angular/angular.js/commit/77cdc37c65491b551fcf01a18ab848a693c293d7))
|
||||
- properly denormalize templates when only one of the start/end symbols is different
|
||||
([2d44a681](https://github.com/angular/angular.js/commit/2d44a681eb912a81a8bc8e16a278c45dae91fa24),
|
||||
[#13848](https://github.com/angular/angular.js/issues/13848))
|
||||
- handle boolean attributes in `@` bindings
|
||||
([2ffbfb0a](https://github.com/angular/angular.js/commit/2ffbfb0ad0647d103ff339ee4b772b62d4823bf3),
|
||||
[#13767](https://github.com/angular/angular.js/issues/13767), [#13769](https://github.com/angular/angular.js/issues/13769))
|
||||
- **$parse:**
|
||||
- prevent assignment on constructor properties
|
||||
([f47e2180](https://github.com/angular/angular.js/commit/f47e218006029f39b4785d820b430de3a0eebcb0),
|
||||
[#13417](https://github.com/angular/angular.js/issues/13417))
|
||||
- preserve expensive checks when runnning `$eval` inside an expression
|
||||
([96d62cc0](https://github.com/angular/angular.js/commit/96d62cc0fc77248d7e3ec4aa458bac0d3e072629))
|
||||
- copy `inputs` for expressions with expensive checks
|
||||
([0b7fff30](https://github.com/angular/angular.js/commit/0b7fff303f46202bbae1ff3ca9d0e5fa76e0fc9a))
|
||||
- **$rootScope:** set no context when calling helper functions for `$watch`
|
||||
([ab5c7698](https://github.com/angular/angular.js/commit/ab5c7698bb106669ca31b5f79a95afa54d65c5f1))
|
||||
- **$route:** allow preventing a route reload
|
||||
([4bc30314](https://github.com/angular/angular.js/commit/4bc3031497447ad527356f12bd0ceee1d7d09db5),
|
||||
[#9824](https://github.com/angular/angular.js/issues/9824), [#13894](https://github.com/angular/angular.js/issues/13894))
|
||||
- **$routeProvider:** properly handle optional eager path named groups
|
||||
([6a4403a1](https://github.com/angular/angular.js/commit/6a4403a11845173d6a96232f77d73aa544b182af),
|
||||
[#14011](https://github.com/angular/angular.js/issues/14011))
|
||||
- **copy:** add support for copying `Blob` objects
|
||||
([863a4232](https://github.com/angular/angular.js/commit/863a4232a6faa92428df45cd54d5a519be2434de),
|
||||
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
|
||||
- **dateFilter:** follow the CLDR on pattern escape sequences
|
||||
([f476060d](https://github.com/angular/angular.js/commit/f476060de6cc016380c0343490a184543f853652),
|
||||
[#12839](https://github.com/angular/angular.js/issues/12839))
|
||||
- **dateFilter, input:** fix Date parsing in IE/Edge when timezone offset contains `:`
|
||||
([571afd65](https://github.com/angular/angular.js/commit/571afd6558786d7b99e2aebd307b4a94c9f2bb87),
|
||||
[#13880](https://github.com/angular/angular.js/issues/13880), [#13887](https://github.com/angular/angular.js/issues/13887))
|
||||
- **input:** re-validate when partially editing date-family inputs
|
||||
([02929f82](https://github.com/angular/angular.js/commit/02929f82f30449301ff18fea84a6396a017683b1),
|
||||
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
|
||||
- **select:** handle corner case of adding options via a custom directive
|
||||
([df6e7315](https://github.com/angular/angular.js/commit/df6e731506831a3dc7f44c9a90abe17515450b3e),
|
||||
[#13874](https://github.com/angular/angular.js/issues/13874), [#13878](https://github.com/angular/angular.js/issues/13878))
|
||||
- **ngOptions:** always set the 'selected' attribute for selected options
|
||||
([f87e8288](https://github.com/angular/angular.js/commit/f87e8288fb69526fd240a66a046f5de52ed204de),
|
||||
[#14115](https://github.com/angular/angular.js/issues/14115))
|
||||
- **ngAnimate:** properly cancel previously running class-based animations
|
||||
([3b27dd37](https://github.com/angular/angular.js/commit/3b27dd37a2cc8a52992784ece6b371023dadf792),
|
||||
[#10156](https://github.com/angular/angular.js/issues/10156), [#13822](https://github.com/angular/angular.js/issues/13822))
|
||||
- **ngAnimateChildren:** make it compatible with `ngIf`
|
||||
([dc158e7e](https://github.com/angular/angular.js/commit/dc158e7e40624ef94c66560386522ef7e991a9ce),
|
||||
[#13865](https://github.com/angular/angular.js/issues/13865), [#13876](https://github.com/angular/angular.js/issues/13876))
|
||||
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
|
||||
([947cb4d1](https://github.com/angular/angular.js/commit/947cb4d1451afa4f5090a693df5b1968dd0df70c),
|
||||
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$locale:** Include original locale ID in $locale
|
||||
([e69f3550](https://github.com/angular/angular.js/commit/e69f35507e10c994708ce4f1efba7573951d1acd),
|
||||
[#13390](https://github.com/angular/angular.js/issues/13390))
|
||||
- **ngAnimate:** provide ng-[event]-prepare class for structural animations
|
||||
([796f7ab4](https://github.com/angular/angular.js/commit/796f7ab41487e124b5b0c02dbf0a03bd581bf073))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** avoid needless overhead when wrapping text nodes
|
||||
([946d9ae9](https://github.com/angular/angular.js/commit/946d9ae90bb31fe911ebbe1b80cd4c8af5a665c6))
|
||||
- **ngRepeat:** avoid duplicate jqLite wrappers
|
||||
([d04c38c4](https://github.com/angular/angular.js/commit/d04c38c48968db777c3ea6a177ce2ff0116df7b4))
|
||||
- **ngAnimate:**
|
||||
- avoid jqLite/jQuery for upward DOM traversal
|
||||
([ab95ba65](https://github.com/angular/angular.js/commit/ab95ba65c08b38cace83de6717b7681079182b45))
|
||||
- avoid `$.fn.data` overhead with jQuery
|
||||
([86416bcb](https://github.com/angular/angular.js/commit/86416bcbee2192fa31c017163c5d856763182ade))
|
||||
|
||||
|
||||
<a name="1.4.9"></a>
|
||||
# 1.4.9 implicit-superannuation (2016-01-21)
|
||||
|
||||
@@ -818,6 +1000,44 @@ describe('$q.when', function() {
|
||||
[#11580](https://github.com/angular/angular.js/issues/11580), [#12234](https://github.com/angular/angular.js/issues/12234))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngInclude:** due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
|
||||
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
|
||||
|
||||
Before:
|
||||
```html
|
||||
<div ng-include="findTemplate('https://example.com/myTemplate.html')"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
$scope.findTemplate = function(templateName) {
|
||||
return $sce.trustAsResourceUrl(templateName);
|
||||
};
|
||||
```
|
||||
|
||||
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
|
||||
whitelist in the `config()` function:
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
var templateCache = {};
|
||||
$scope.findTemplate = function(templateName) {
|
||||
if (!templateCache[templateName]) {
|
||||
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
|
||||
}
|
||||
|
||||
return templateCache[templateName];
|
||||
};
|
||||
|
||||
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`:
|
||||
|
||||
angular.module('myApp', []).config(function($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/**'])
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.17"></a>
|
||||
# 1.3.17 tsktskskly-euouae (2015-07-06)
|
||||
|
||||
+1
-1
@@ -314,7 +314,7 @@ module.exports = function(grunt) {
|
||||
stderr: true,
|
||||
failOnError: true
|
||||
},
|
||||
command: path.normalize('./node_modules/.bin/promises-aplus-tests tmp/promises-aplus-adapter++.js')
|
||||
command: path.normalize('./node_modules/.bin/promises-aplus-tests tmp/promises-aplus-adapter++.js --timeout 2000')
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ angular.module('tutorials', [])
|
||||
scope: {},
|
||||
template:
|
||||
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{diffLo}}...1.4-step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
|
||||
link: function(scope, element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav;
|
||||
@@ -38,12 +37,11 @@ angular.module('tutorials', [])
|
||||
'<p><button class="btn" ng-click="show=!show">Workspace Reset Instructions ➤</button></p>\n' +
|
||||
'<div class="alert alert-info" ng-show="show">\n' +
|
||||
' <p>Reset the workspace to step {{step}}.</p>' +
|
||||
' <p><pre>git checkout -f step-{{step}}</pre></p>\n' +
|
||||
' <p>Refresh your browser or check out this step online: '+
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
|
||||
' <p><pre>git checkout -f 1.4-step-{{step}}</pre></p>\n' +
|
||||
' <p>Refresh your browser to see the changes.</p>\n' +
|
||||
'</div>\n' +
|
||||
'<p>The most important changes are listed below. You can see the full diff on ' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}" title="See diff on Github">GitHub</a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{step ? (step - 1) : \'0~1\'}}...1.4-step-{{step}}" title="See diff on Github">GitHub</a>\n' +
|
||||
'</p>'
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -350,7 +350,7 @@ base and the path that should be handled by the application.
|
||||
### Base href constraints
|
||||
|
||||
The `$location` service is not able to function properly if the current URL is outside the URL given
|
||||
as the base href. This can have subtle confusing consequencies...
|
||||
as the base href. This can have subtle confusing consequences...
|
||||
|
||||
Consider a base href set as follows: `<base href="/base/">` (i.e. the application exists in the "folder"
|
||||
called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
@ngdoc overview
|
||||
@name Decorators
|
||||
@sortOrder 345
|
||||
@description
|
||||
|
||||
# Decorators in AngularJS
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**NOTE:** This guide is targeted towards developers who are already familiar with AngularJS basics.
|
||||
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
|
||||
</div>
|
||||
|
||||
## What are decorators?
|
||||
|
||||
Decorators are a design pattern that is used to separate modification or *decoration* of a class without modifying the
|
||||
original source code. In Angular, decorators are functions that allow a service, directive or filter to be modified
|
||||
prior to its usage.
|
||||
|
||||
## How to use decorators
|
||||
|
||||
There are two ways to register decorators
|
||||
|
||||
- `$provide.decorator`, and
|
||||
- `module.decorator`
|
||||
|
||||
Each provide access to a `$delegate`, which is the instantiated service/directive/filter, prior to being passed to the
|
||||
service that required it.
|
||||
|
||||
### $provide.decorator
|
||||
|
||||
The {@link api/auto/service/$provide#decorator decorator function} allows access to a $delegate of the service once it
|
||||
has been instantiated. For example:
|
||||
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('$log', [
|
||||
'$delegate',
|
||||
function $logDecorator($delegate) {
|
||||
|
||||
var originalWarn = $delegate.warn;
|
||||
$delegate.warn = function decoratedWarn(msg) {
|
||||
msg = 'Decorated Warn: ' + msg;
|
||||
originalWarn.apply($delegate, arguments);
|
||||
};
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
After the `$log` service has been instantiated the decorator is fired. The decorator function has a `$delegate` object
|
||||
injected to provide access to the service that matches the selector in the decorator. This `$delegate` will be the
|
||||
service you are decorating. The return value of the function *provided to the decorator* will take place of the service,
|
||||
directive, or filter being decorated.
|
||||
|
||||
<hr>
|
||||
|
||||
The `$delegate` may be either modified or completely replaced. Given a service `myService` with a method `someFn`, the
|
||||
following could all be viable solutions:
|
||||
|
||||
|
||||
#### Completely Replace the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
var myDecoratedService = {
|
||||
// new service object to replace myService
|
||||
};
|
||||
return myDecoratedService;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
#### Patch the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
var someFn = $delegate.someFn;
|
||||
|
||||
function aNewFn() {
|
||||
// new service function
|
||||
someFn.apply($delegate, arguments);
|
||||
}
|
||||
|
||||
$delegate.someFn = aNewFn;
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
#### Augment the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
function helperFn() {
|
||||
// an additional fn to add to the service
|
||||
}
|
||||
|
||||
$delegate.aHelpfulAddition = helperFn;
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a
|
||||
missing return statement will wipe out the entire object being decorated.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
Decorators have different rules for different services. This is because services are registered in different ways.
|
||||
Services are selected by name, however filters and directives are selected by appending `"Filter"` or `"Directive"` to
|
||||
the end of the name. The `$delegate` provided is dictated by the type of service.
|
||||
|
||||
| Service Type | Selector | $delegate |
|
||||
|--------------|-------------------------------|-----------------------------------------------------------------------|
|
||||
| Service | `serviceName` | The `object` or `function` returned by the service |
|
||||
| Directive | `directiveName + 'Directive'` | An `Array.<DirectiveObject>`<sub>{@link guide/decorators#drtvArray 1}</sub> |
|
||||
| Filter | `filterName + 'Filter'` | The `function` returned by the filter |
|
||||
|
||||
<small id="drtvArray">1. Multiple directives may be registered to the same selector/name</small>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**NOTE:** Developers should take care in how and why they are modifying the `$delegate` for the service. Not only
|
||||
should expectations for the consumer be kept, but some functionality (such as directive registration) does not take
|
||||
place after decoration, but during creation/registration of the original service. This means, for example, that
|
||||
an action such as pushing a directive object to a directive `$delegate` will likely result in unexpected behavior.
|
||||
|
||||
Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly
|
||||
or adversely affect the functionality of the framework.
|
||||
</div>
|
||||
|
||||
### module.decorator
|
||||
|
||||
This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is
|
||||
exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. The
|
||||
main caveat here is that you will need to take note the order in which you create your decorators.
|
||||
|
||||
Unlike in the module config block (which allows configuration of services prior to their creation), the service must be
|
||||
registered prior to the decorator (see {@link guide/providers#provider-recipe Provider Recipe}). For example, the
|
||||
following would not work because you are attempting to decorate outside of the configuration phase and the service
|
||||
hasn't been created yet:
|
||||
|
||||
```js
|
||||
// will cause an error since 'someService' hasn't been registered
|
||||
angular.module('myApp').decorator('someService', ...);
|
||||
|
||||
angular.module('myApp').factory('someService', ...);
|
||||
```
|
||||
|
||||
## Example Applications
|
||||
|
||||
The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator.
|
||||
|
||||
### Service Decorator Example
|
||||
|
||||
This example shows how we can replace the $log service with our own to display log messages.
|
||||
|
||||
<example module="myServiceDecorator" name="service-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('myServiceDecorator', []).
|
||||
|
||||
controller('Ctrl', [
|
||||
'$scope',
|
||||
'$log',
|
||||
'$timeout',
|
||||
function($scope, $log, $timeout) {
|
||||
var types = ['error', 'warn', 'log', 'info' ,'debug'], i;
|
||||
|
||||
for (i = 0; i < types.length; i++) {
|
||||
$log[types[i]](types[i] + ': message ' + (i + 1));
|
||||
}
|
||||
|
||||
$timeout(function() {
|
||||
$log.info('info: message logged in timeout');
|
||||
});
|
||||
}
|
||||
]).
|
||||
|
||||
directive('myLog', [
|
||||
'$log',
|
||||
function($log) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<ul id="myLog"><li ng-repeat="l in myLog" class="{{l.type}}">{{l.message}}</li></ul>',
|
||||
scope: {},
|
||||
compile: function() {
|
||||
return function(scope) {
|
||||
scope.myLog = $log.stack;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
]).
|
||||
|
||||
config([
|
||||
'$provide',
|
||||
function($provide) {
|
||||
|
||||
$provide.decorator('$log', [
|
||||
'$delegate',
|
||||
function logDecorator($delegate) {
|
||||
|
||||
var myLog = {
|
||||
warn: function(msg) {
|
||||
log(msg, 'warn');
|
||||
},
|
||||
error: function(msg) {
|
||||
log(msg, 'error');
|
||||
},
|
||||
info: function(msg) {
|
||||
log(msg, 'info');
|
||||
},
|
||||
debug: function(msg) {
|
||||
log(msg, 'debug');
|
||||
},
|
||||
log: function(msg) {
|
||||
log(msg, 'log');
|
||||
},
|
||||
stack: []
|
||||
};
|
||||
|
||||
function log(msg, type) {
|
||||
myLog.stack.push({ type: type, message: msg.toString() });
|
||||
if (console && console[type]) console[type](msg);
|
||||
}
|
||||
|
||||
return myLog;
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
}
|
||||
]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<h1>Logs</h1>
|
||||
<my-log></my-log>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="style.css">
|
||||
li.warn { color: yellow; }
|
||||
li.error { color: red; }
|
||||
li.info { color: blue }
|
||||
li.log { color: black }
|
||||
li.debug { color: green }
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should display log messages in dom', function() {
|
||||
element.all(by.repeater('l in myLog')).count().then(function(count) {
|
||||
expect(count).toEqual(6);
|
||||
});
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Directive Decorator Example
|
||||
|
||||
Failed interpolated expressions in `ng-href` attributes can easily go unnoticed. We can decorate `ngHref` to warn us of
|
||||
those conditions.
|
||||
|
||||
<example module="urlDecorator" name="directive-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('urlDecorator', []).
|
||||
|
||||
controller('Ctrl', ['$scope', function ($scope) {
|
||||
$scope.id = 3;
|
||||
$scope.warnCount = 0; // for testing
|
||||
}]).
|
||||
|
||||
config(['$provide', function($provide) {
|
||||
|
||||
// matchExpressions looks for interpolation markup in the directive attribute, extracts the expressions
|
||||
// from that markup (if they exist) and returns an array of those expressions
|
||||
function matchExpressions(str) {
|
||||
var exps = str.match(/{{([^}]+)}}/g);
|
||||
|
||||
// if there isn't any, get out of here
|
||||
if (exps === null) return;
|
||||
|
||||
exps = exps.map(function(exp) {
|
||||
var prop = exp.match(/[^{}]+/);
|
||||
return prop === null ? null : prop[0];
|
||||
});
|
||||
|
||||
return exps;
|
||||
}
|
||||
|
||||
// remember: directives must be selected by appending 'Directive' to the directive selector
|
||||
$provide.decorator('ngHrefDirective', [
|
||||
'$delegate',
|
||||
'$log',
|
||||
'$parse',
|
||||
function($delegate, $log, $parse) {
|
||||
|
||||
// store the original link fn
|
||||
var originalLinkFn = $delegate[0].link;
|
||||
|
||||
// replace the compile fn
|
||||
$delegate[0].compile = function(tElem, tAttr) {
|
||||
|
||||
// store the original exp in the directive attribute for our warning message
|
||||
var originalExp = tAttr.ngHref;
|
||||
|
||||
// get the interpolated expressions
|
||||
var exps = matchExpressions(originalExp);
|
||||
|
||||
// create and store the getters using $parse
|
||||
var getters = exps.map(function(el) {
|
||||
if (el) return $parse(el);
|
||||
});
|
||||
|
||||
return function newLinkFn(scope, elem, attr) {
|
||||
// fire the originalLinkFn
|
||||
originalLinkFn.apply($delegate[0], arguments);
|
||||
|
||||
// observe the directive attr and check the expressions
|
||||
attr.$observe('ngHref', function(val) {
|
||||
|
||||
// if we have getters and getters is an array...
|
||||
if (getters && angular.isArray(getters)) {
|
||||
|
||||
// loop through the getters and process them
|
||||
angular.forEach(getters, function(g, idx) {
|
||||
|
||||
// if val is truthy, then the warning won't log
|
||||
var val = angular.isFunction(g) ? g(scope) : true;
|
||||
if (!val) {
|
||||
$log.warn('NgHref Warning: "' + exps[idx] + '" in the expression "' + originalExp +
|
||||
'" is falsy!');
|
||||
|
||||
scope.warnCount++; // for testing
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// get rid of the old link function since we return a link function in compile
|
||||
delete $delegate[0].link;
|
||||
|
||||
// return the $delegate
|
||||
return $delegate;
|
||||
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<a ng-href="/products/{{ id }}/view" id="id3">View Product {{ id }}</a>
|
||||
- <strong>id == 3</strong>, so no warning<br>
|
||||
<a ng-href="/products/{{ id + 5 }}/view" id="id8">View Product {{ id + 5 }}</a>
|
||||
- <strong>id + 5 == 8</strong>, so no warning<br>
|
||||
<a ng-href="/products/{{ someOtherId }}/view" id="someOtherId">View Product {{ someOtherId }}</a>
|
||||
- <strong style="background-color: #ffff00;">someOtherId == undefined</strong>, so warn<br>
|
||||
<a ng-href="/products/{{ someOtherId + 5 }}/view" id="someOtherId5">View Product {{ someOtherId + 5 }}</a>
|
||||
- <strong>someOtherId + 5 == 5</strong>, so no warning<br>
|
||||
<div>Warn Count: {{ warnCount }}</div>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should warn when an expression in the interpolated value is falsy', function() {
|
||||
var id3 = element(by.id('id3'));
|
||||
var id8 = element(by.id('id8'));
|
||||
var someOther = element(by.id('someOtherId'));
|
||||
var someOther5 = element(by.id('someOtherId5'));
|
||||
|
||||
expect(id3.getText()).toEqual('View Product 3');
|
||||
expect(id3.getAttribute('href')).toContain('/products/3/view');
|
||||
|
||||
expect(id8.getText()).toEqual('View Product 8');
|
||||
expect(id8.getAttribute('href')).toContain('/products/8/view');
|
||||
|
||||
expect(someOther.getText()).toEqual('View Product');
|
||||
expect(someOther.getAttribute('href')).toContain('/products//view');
|
||||
|
||||
expect(someOther5.getText()).toEqual('View Product 5');
|
||||
expect(someOther5.getAttribute('href')).toContain('/products/5/view');
|
||||
|
||||
expect(element(by.binding('warnCount')).getText()).toEqual('Warn Count: 1');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Filter Decorator Example
|
||||
|
||||
Let's say we have created an app that uses the default format for many of our `Date` filters. Suddenly requirements have
|
||||
changed (that never happens) and we need all of our default dates to be `'shortDate'` instead of `'mediumDate'`.
|
||||
|
||||
<example module="filterDecorator" name="filter-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('filterDecorator', []).
|
||||
|
||||
controller('Ctrl', ['$scope', function ($scope) {
|
||||
$scope.genesis = new Date(2010, 0, 5);
|
||||
$scope.ngConf = new Date(2016, 4, 4);
|
||||
}]).
|
||||
|
||||
config(['$provide', function($provide) {
|
||||
|
||||
$provide.decorator('dateFilter', [
|
||||
'$delegate',
|
||||
function dateDecorator($delegate) {
|
||||
|
||||
// store the original filter
|
||||
var originalFilter = $delegate;
|
||||
|
||||
// return our filter
|
||||
return shortDateDefault;
|
||||
|
||||
// shortDateDefault sets the format to shortDate if it is falsy
|
||||
function shortDateDefault(date, format, timezone) {
|
||||
if (!format) format = 'shortDate';
|
||||
|
||||
// return the result of the original filter
|
||||
return originalFilter(date, format, timezone);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div id="genesis">Initial Commit default to short date: {{ genesis | date }}</div>
|
||||
<div>ng-conf 2016 default short date: {{ ngConf | date }}</div>
|
||||
<div id="ngConf">ng-conf 2016 with full date format: {{ ngConf | date:'fullDate' }}</div>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should default date filter to short date format', function() {
|
||||
expect(element(by.id('genesis')).getText())
|
||||
.toMatch(/Initial Commit default to short date: \d{1,2}\/\d{1,2}\/\d{2}/);
|
||||
});
|
||||
|
||||
it('should still allow dates to be formatted', function() {
|
||||
expect(element(by.id('ngConf')).getText())
|
||||
.toMatch(/ng-conf 2016 with full date format\: [A-Za-z]+, [A-Za-z]+ \d{1,2}, \d{4}/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -101,7 +101,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
|
||||
## Learning Resources
|
||||
|
||||
###Books
|
||||
### Books
|
||||
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
|
||||
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
@@ -113,7 +113,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
|
||||
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
|
||||
|
||||
###Videos:
|
||||
### Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
* [Angular on YouTube](http://youtube.com/angularjs)
|
||||
* [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/)
|
||||
|
||||
@@ -284,7 +284,7 @@ angular.module('myApp').directive('form', function() {
|
||||
});
|
||||
```
|
||||
|
||||
## Templating (`ngRepeat`, `$compile`)
|
||||
## Templating (`ngRepeat`, `$compile`, `ngInclude`)
|
||||
|
||||
### ngRepeat
|
||||
|
||||
@@ -316,6 +316,45 @@ returning an object from a controller constructor function will now override the
|
||||
controllerAs method will no longer get the this reference, but the returned object.
|
||||
|
||||
|
||||
### ngInclude:
|
||||
Due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
|
||||
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
|
||||
|
||||
Before:
|
||||
```html
|
||||
<div ng-include="findTemplate('https://example.com/templates/myTemplate.html')"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
$scope.findTemplate = function(templateName) {
|
||||
return $sce.trustAsResourceUrl(templateName);
|
||||
};
|
||||
```
|
||||
|
||||
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
|
||||
whitelist in the `config()` function:
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
var templateCache = {};
|
||||
$scope.findTemplate = function(templateName) {
|
||||
if (!templateCache[templateName]) {
|
||||
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
|
||||
}
|
||||
|
||||
return templateCache[templateName];
|
||||
};
|
||||
|
||||
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`, which means you don't
|
||||
// have to use `$sce.trustAsResourceUrl()` at all:
|
||||
|
||||
angular.module('myApp', []).config(function($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Cookies (`ngCookies`)
|
||||
|
||||
Due to [38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
|
||||
@@ -353,8 +392,6 @@ has been moved to `$cookies`, to which `$cookieStore` now simply
|
||||
delegates calls.
|
||||
|
||||
|
||||
|
||||
|
||||
## Server Requests (`$http`)
|
||||
|
||||
Due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
@@ -386,8 +423,6 @@ $http.get(url, {
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Filters (`filter`, `limitTo`)
|
||||
|
||||
### `filter` filter
|
||||
@@ -405,8 +440,6 @@ Now, instead of returning empty object/array, it returns unchanged input.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
## Controllers
|
||||
|
||||
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
|
||||
|
||||
2. **Watcher registration**
|
||||
|
||||
During template linking directives register {@link
|
||||
During template linking, directives register {@link
|
||||
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
|
||||
used to propagate model values to the DOM.
|
||||
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
|
||||
# PhoneCat Tutorial App
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<p>
|
||||
This version of the tutorial is deprecated and no longer maintained. You can find the most
|
||||
recent version of the tutorial at https://docs.angularjs.org/tutorial/.
|
||||
</p>
|
||||
<p>
|
||||
It is recommended to use the latest tutorial version for the following reasons:
|
||||
<ul>
|
||||
<li>It showcases features introduced in AngularJS v1.5+</li>
|
||||
<li>It follows modern best practices in terms of architecture and code organization</li>
|
||||
<li>It has more up-to-date dependencies and tools</li>
|
||||
<li>It is actively maintained</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
|
||||
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
|
||||
of Android devices, lets you filter the list to see only devices that interest you, and then view
|
||||
@@ -73,7 +89,7 @@ Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by r
|
||||
command:
|
||||
|
||||
```
|
||||
git clone --depth=14 https://github.com/angular/angular-phonecat.git
|
||||
git clone --depth=14 --branch=1.4-snapshot https://github.com/angular/angular-phonecat.git
|
||||
```
|
||||
|
||||
This command creates the `angular-phonecat` directory in your current directory.
|
||||
@@ -82,6 +98,11 @@ This command creates the `angular-phonecat` directory in your current directory.
|
||||
download much smaller and faster.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
The `--branch=1.4-snapshot` option tells Git to pull down the `1.4-snapshot` branch.
|
||||
The code for this older version of the tutorial is on that branch.
|
||||
</div>
|
||||
|
||||
Change your current directory to `angular-phonecat`.
|
||||
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ dependencies, as described in {@link index#get-started Get Started}.
|
||||
In the `angular-phonecat` directory, run this command:
|
||||
|
||||
```
|
||||
git checkout -f step-0
|
||||
git checkout -f 1.4-step-0
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ config.specs = [
|
||||
'app/e2e/**/*.scenario.js'
|
||||
];
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome',
|
||||
};
|
||||
config.capabilities.browserName = 'chrome';
|
||||
|
||||
exports.config = config;
|
||||
|
||||
+2848
-4793
File diff suppressed because it is too large
Load Diff
Generated
+7807
-10846
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -53,7 +53,7 @@
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-node": "^2.0.0",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jasmine-reporters": "^2.2.0",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "^0.13.19",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
@@ -69,7 +69,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "^2.1.0",
|
||||
"protractor": "^4.0.11",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
|
||||
+1
-3
@@ -8,8 +8,6 @@ config.specs = [
|
||||
'docs/app/e2e/**/*.scenario.js'
|
||||
];
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome'
|
||||
};
|
||||
config.capabilities.browserName = 'chrome';
|
||||
|
||||
exports.config = config;
|
||||
|
||||
@@ -15,7 +15,7 @@ exports.config = {
|
||||
|
||||
baseUrl: 'http://localhost:8000/',
|
||||
|
||||
framework: 'jasmine',
|
||||
framework: 'jasmine2',
|
||||
|
||||
onPrepare: function() {
|
||||
/* global angular: false, browser: false, jasmine: false */
|
||||
@@ -29,9 +29,10 @@ exports.config = {
|
||||
|
||||
browser.addMockModule('disableNgAnimate', disableNgAnimate);
|
||||
|
||||
require('jasmine-reporters');
|
||||
jasmine.getEnv().addReporter(
|
||||
new jasmine.JUnitXmlReporter('test_out/docs-e2e-' + exports.config.capabilities.browserName + '-', true, true));
|
||||
var reporters = require('jasmine-reporters');
|
||||
jasmine.getEnv().addReporter(new reporters.JUnitXmlReporter({
|
||||
savePath: 'test_out/docs-e2e-' + exports.config.capabilities.browserName + '-'
|
||||
}));
|
||||
},
|
||||
|
||||
jasmineNodeOpts: {
|
||||
|
||||
@@ -5,7 +5,12 @@ exports.config = {
|
||||
|
||||
baseUrl: 'http://localhost:8000/',
|
||||
|
||||
framework: 'jasmine',
|
||||
framework: 'jasmine2',
|
||||
|
||||
capabilities: {
|
||||
// Fix element scrolling behavior in Firefox for fixed header elements (like angularjs.org has)
|
||||
'elementScrollBehavior': 1
|
||||
},
|
||||
|
||||
onPrepare: function() {
|
||||
/* global angular: false, browser: false, jasmine: false */
|
||||
|
||||
+13
-11
@@ -9,16 +9,16 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
version: '49'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
version: '47'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
@@ -28,17 +28,17 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
platform: 'OS X 10.11',
|
||||
version: '48'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
version: '47'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
platform: 'OS X 10.11',
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -54,7 +54,7 @@ function capabilitiesForBrowserStack(capabilities) {
|
||||
return {
|
||||
'browserstack.user': process.env.BROWSER_STACK_USERNAME,
|
||||
'browserstack.key': process.env.BROWSER_STACK_ACCESS_KEY,
|
||||
'browserstack.local' : 'true',
|
||||
'browserstack.local': 'true',
|
||||
'browserstack.debug': 'true',
|
||||
'browserstack.tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
@@ -64,7 +64,8 @@ function capabilitiesForBrowserStack(capabilities) {
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
'version': capabilities.version,
|
||||
'elementScrollBehavior': 1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,6 +78,7 @@ function capabilitiesForSauceLabs(capabilities) {
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
'version': capabilities.version,
|
||||
'elementScrollBehavior': 1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ echo "#################################"
|
||||
echo "#### Jenkins Build ############"
|
||||
echo "#################################"
|
||||
|
||||
source scripts/jenkins/set-node-version.sh
|
||||
|
||||
# Enable tracing and exit on first failure
|
||||
set -xe
|
||||
|
||||
scripts/jenkins/set-node-version.sh
|
||||
|
||||
# This is the default set of browsers to use on the CI server unless overridden via env variable
|
||||
if [[ -z "$BROWSERS" ]]
|
||||
then
|
||||
|
||||
@@ -35,7 +35,7 @@ function init {
|
||||
}
|
||||
|
||||
function build {
|
||||
./set-node-version.sh
|
||||
source ./set-node-version.sh
|
||||
cd ../..
|
||||
|
||||
npm install -g grunt-cli
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
source ~/.nvm/nvm.sh
|
||||
|
||||
# Use node.js at 4.2.x
|
||||
nvm install 4.2
|
||||
nvm install 4.4
|
||||
|
||||
+16
-6
@@ -466,12 +466,22 @@ noop.$inject = [];
|
||||
* functional style.
|
||||
*
|
||||
```js
|
||||
function transformer(transformationFn, value) {
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
function transformer(transformationFn, value) {
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
|
||||
// E.g.
|
||||
function getResult(fn, input) {
|
||||
return (fn || angular.identity)(input);
|
||||
};
|
||||
|
||||
getResult(function(n) { return n * 2; }, 21); // returns 42
|
||||
getResult(null, 21); // returns 21
|
||||
getResult(undefined, 21); // returns 21
|
||||
```
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
@@ -1594,7 +1604,7 @@ function bootstrap(element, modules, config) {
|
||||
//Encode angle brackets to prevent input from being sanitized to empty string #8683
|
||||
throw ngMinErr(
|
||||
'btstrpd',
|
||||
"App Already Bootstrapped with this Element '{0}'",
|
||||
"App already bootstrapped with this element '{0}'",
|
||||
tag.replace(/</,'<').replace(/>/,'>'));
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -197,9 +197,9 @@ function setupModuleLoader(window) {
|
||||
* @ngdoc method
|
||||
* @name angular.Module#decorator
|
||||
* @module ng
|
||||
* @param {string} The name of the service to decorate.
|
||||
* @param {Function} This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @param {string} name The name of the service to decorate.
|
||||
* @param {Function} decorFn This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @description
|
||||
* See {@link auto.$provide#decorator $provide.decorator()}.
|
||||
*/
|
||||
|
||||
@@ -41,7 +41,7 @@ function $AnchorScrollProvider() {
|
||||
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
|
||||
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
|
||||
* in the
|
||||
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
|
||||
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
|
||||
*
|
||||
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
|
||||
* match any anchor whenever it changes. This can be disabled by calling
|
||||
|
||||
+13
-3
@@ -782,7 +782,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
|
||||
|
||||
var bindings = {};
|
||||
var bindings = createMap();
|
||||
|
||||
forEach(scope, function(definition, scopeName) {
|
||||
if (definition in bindingCache) {
|
||||
@@ -1153,7 +1153,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, key === 'src');
|
||||
} else if (nodeName === 'img' && key === 'srcset') {
|
||||
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
|
||||
// sanitize img[srcset] values
|
||||
var result = "";
|
||||
|
||||
@@ -1538,13 +1538,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var nodeType = node.nodeType,
|
||||
attrsMap = attrs.$attr,
|
||||
match,
|
||||
nodeName,
|
||||
className;
|
||||
|
||||
switch (nodeType) {
|
||||
case NODE_TYPE_ELEMENT: /* Element */
|
||||
|
||||
nodeName = nodeName_(node);
|
||||
|
||||
// use the node name: <directive>
|
||||
addDirective(directives,
|
||||
directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
|
||||
directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
|
||||
@@ -1585,6 +1589,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attrEndName);
|
||||
}
|
||||
|
||||
if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
|
||||
// Hidden input elements can have strange behaviour when navigating back to the page
|
||||
// This tells the browser not to try to cache and reinstate previous values
|
||||
node.setAttribute('autocomplete', 'off');
|
||||
}
|
||||
|
||||
// use class as directive
|
||||
className = node.className;
|
||||
if (isObject(className)) {
|
||||
|
||||
@@ -393,7 +393,7 @@ var inputType = {
|
||||
}]);
|
||||
</script>
|
||||
<form name="myForm" ng-controller="DateController as dateCtrl">
|
||||
<label for="exampleInput">Pick a between 8am and 5pm:</label>
|
||||
<label for="exampleInput">Pick a time between 8am and 5pm:</label>
|
||||
<input type="time" id="exampleInput" name="input" ng-model="example.value"
|
||||
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
|
||||
<div role="alert">
|
||||
|
||||
@@ -188,8 +188,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
|
||||
restrict: 'A',
|
||||
compile: function ngBindHtmlCompile(tElement, tAttrs) {
|
||||
var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
|
||||
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
|
||||
return (value || '').toString();
|
||||
var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
|
||||
// Unwrap the value to compare the actual inner safe value, not the wrapper object.
|
||||
return $sce.valueOf(val);
|
||||
});
|
||||
$compile.$$addBindingClass(tElement);
|
||||
|
||||
@@ -197,9 +198,9 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
|
||||
$compile.$$addBindingInfo(element, attr.ngBindHtml);
|
||||
|
||||
scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
|
||||
// we re-evaluate the expr because we want a TrustedValueHolderType
|
||||
// for $sce, not a string
|
||||
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
|
||||
// The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
|
||||
var value = ngBindHtmlGetter(scope);
|
||||
element.html($sce.getTrustedHtml(value) || '');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,11 @@ function classDirective(name, selector) {
|
||||
updateClasses(oldClasses, newClasses);
|
||||
}
|
||||
}
|
||||
oldVal = shallowCopy(newVal);
|
||||
if (isArray(newVal)) {
|
||||
oldVal = newVal.map(function(v) { return shallowCopy(v); });
|
||||
} else {
|
||||
oldVal = shallowCopy(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngRequired
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
*
|
||||
|
||||
@@ -93,7 +93,9 @@ function currencyFilter($locale) {
|
||||
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
|
||||
* If this is not provided then the fraction size is computed from the current locale's number
|
||||
* formatting pattern. In the case of the default locale, it will be 3.
|
||||
* @returns {string} Number rounded to fractionSize and places a “,” after each third digit.
|
||||
* @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
|
||||
* locale (e.g., in the en_US locale it will have "." as the decimal separator and
|
||||
* include "," group separators after each third digit).
|
||||
*
|
||||
* @example
|
||||
<example module="numberFilterExample">
|
||||
@@ -296,7 +298,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
// extract decimals digits
|
||||
if (integerLen > 0) {
|
||||
decimals = digits.splice(integerLen);
|
||||
decimals = digits.splice(integerLen, digits.length);
|
||||
} else {
|
||||
decimals = digits;
|
||||
digits = [0];
|
||||
@@ -304,11 +306,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
// format the integer digits with grouping separators
|
||||
var groups = [];
|
||||
if (digits.length > pattern.lgSize) {
|
||||
groups.unshift(digits.splice(-pattern.lgSize).join(''));
|
||||
if (digits.length >= pattern.lgSize) {
|
||||
groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
|
||||
}
|
||||
while (digits.length > pattern.gSize) {
|
||||
groups.unshift(digits.splice(-pattern.gSize).join(''));
|
||||
groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
|
||||
}
|
||||
if (digits.length) {
|
||||
groups.unshift(digits.join(''));
|
||||
|
||||
+1
-1
@@ -555,7 +555,7 @@ function $HttpProvider() {
|
||||
* That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
|
||||
* For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
|
||||
* function will be reflected on the scope and in any templates where the object is data-bound.
|
||||
* To prevent his, transform functions should have no side-effects.
|
||||
* To prevent this, transform functions should have no side-effects.
|
||||
* If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
|
||||
* </div>
|
||||
*
|
||||
|
||||
@@ -199,6 +199,11 @@ function $InterpolateProvider() {
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @knownIssue
|
||||
* It is currently not possible for an interpolated expression to contain the interpolation end
|
||||
* symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
|
||||
* an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
|
||||
*
|
||||
* @param {string} text The text with markup to interpolate.
|
||||
* @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
|
||||
* embedded expression in order to return an interpolation function. Strings with no
|
||||
|
||||
+1
-1
@@ -482,7 +482,7 @@ var locationPrototype = {
|
||||
* ```
|
||||
*
|
||||
* @param {(string|number)=} path New path
|
||||
* @return {string} path
|
||||
* @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
path = path !== null ? path.toString() : '';
|
||||
|
||||
+3
-3
@@ -13,15 +13,15 @@
|
||||
* [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
*
|
||||
* $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
|
||||
* implementations, and the other which resembles ES6 promises to some degree.
|
||||
* implementations, and the other which resembles ES6 (ES2015) promises to some degree.
|
||||
*
|
||||
* # $q constructor
|
||||
*
|
||||
* The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
|
||||
* function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
|
||||
* function as the first argument. This is similar to the native Promise implementation from ES6,
|
||||
* see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
*
|
||||
* While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
|
||||
* While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
|
||||
* available yet.
|
||||
*
|
||||
* It can be used like so:
|
||||
|
||||
+2
-2
@@ -627,12 +627,12 @@ function $SceDelegateProvider() {
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* describe('SCE doc demo', function() {
|
||||
* it('should sanitize untrusted values', function() {
|
||||
* expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
|
||||
* expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
|
||||
* .toBe('<span>Is <i>anyone</i> reading this?</span>');
|
||||
* });
|
||||
*
|
||||
* it('should NOT sanitize explicitly trusted values', function() {
|
||||
* expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
|
||||
* expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
|
||||
* '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
|
||||
* 'sanitization."">Hover over this text.</span>');
|
||||
* });
|
||||
|
||||
@@ -31,7 +31,7 @@ function $TemplateRequestProvider() {
|
||||
// are included in there. This also makes Angular accept any script
|
||||
// directive, no matter its name. However, we still need to unwrap trusted
|
||||
// types.
|
||||
if (!isString(tpl) || !$templateCache.get(tpl)) {
|
||||
if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
|
||||
tpl = $sce.getTrustedResourceUrl(tpl);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
var activeAnimationsLookup = new $$HashMap();
|
||||
var disabledElementsLookup = new $$HashMap();
|
||||
var animationsEnabled = null;
|
||||
// $document might be mocked out in tests and won't include a real document.
|
||||
// Providing an empty object with hidden = true will prevent animations from running
|
||||
var rawDocument = $document[0] || {hidden: true};
|
||||
|
||||
function postDigestTaskFactory() {
|
||||
var postDigestCalled = false;
|
||||
@@ -331,12 +334,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
|
||||
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
|
||||
|
||||
var documentHidden = rawDocument.hidden;
|
||||
|
||||
// this is a hard disable of all animations for the application or on
|
||||
// the element itself, therefore there is no need to continue further
|
||||
// past this point if not enabled
|
||||
// Animations are also disabled if the document is currently hidden (page is not visible
|
||||
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame
|
||||
var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
|
||||
var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
|
||||
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
|
||||
var hasExistingAnimation = !!existingAnimation.state;
|
||||
|
||||
@@ -347,7 +352,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (skipAnimations) {
|
||||
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
|
||||
if (documentHidden) notifyProgress(runner, event, 'start');
|
||||
close();
|
||||
if (documentHidden) notifyProgress(runner, event, 'close');
|
||||
return runner;
|
||||
}
|
||||
|
||||
@@ -497,6 +505,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
markElementAnimationState(element, RUNNING_STATE);
|
||||
var realRunner = $$animation(element, event, animationDetails.options);
|
||||
|
||||
// this will update the runner's flow-control events based on
|
||||
// the `realRunner` object.
|
||||
runner.setHost(realRunner);
|
||||
notifyProgress(runner, event, 'start', {});
|
||||
|
||||
realRunner.done(function(status) {
|
||||
close(!status);
|
||||
var animationDetails = activeAnimationsLookup.get(node);
|
||||
@@ -505,11 +518,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
notifyProgress(runner, event, 'close', {});
|
||||
});
|
||||
|
||||
// this will update the runner's flow-control events based on
|
||||
// the `realRunner` object.
|
||||
runner.setHost(realRunner);
|
||||
notifyProgress(runner, event, 'start', {});
|
||||
});
|
||||
|
||||
return runner;
|
||||
@@ -578,7 +586,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
* d) the element is not a child of the $rootElement
|
||||
*/
|
||||
function areAnimationsAllowed(element, parentElement, event) {
|
||||
var bodyElement = jqLite($document[0].body);
|
||||
var bodyElement = jqLite(rawDocument.body);
|
||||
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
|
||||
var rootElementDetected = isMatchingElement(element, $rootElement);
|
||||
var parentAnimationDetected = false;
|
||||
|
||||
@@ -127,7 +127,7 @@ function stripCommentsFromElement(element) {
|
||||
if (element instanceof jqLite) {
|
||||
switch (element.length) {
|
||||
case 0:
|
||||
return [];
|
||||
return element;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
||||
@@ -410,6 +410,13 @@ angular.module('ngMessages', [])
|
||||
|
||||
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
|
||||
|
||||
// If the element is destroyed, proactively destroy all the currently visible messages
|
||||
$element.on('$destroy', function() {
|
||||
forEach(messages, function(item) {
|
||||
item.message.detach();
|
||||
});
|
||||
});
|
||||
|
||||
this.reRender = function() {
|
||||
if (!renderLater) {
|
||||
renderLater = true;
|
||||
@@ -444,6 +451,7 @@ angular.module('ngMessages', [])
|
||||
function findPreviousMessage(parent, comment) {
|
||||
var prevNode = comment;
|
||||
var parentLookup = [];
|
||||
|
||||
while (prevNode && prevNode !== parent) {
|
||||
var prevKey = prevNode.$$ngMessageNode;
|
||||
if (prevKey && prevKey.length) {
|
||||
@@ -455,8 +463,11 @@ angular.module('ngMessages', [])
|
||||
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
|
||||
parentLookup.push(prevNode);
|
||||
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
|
||||
} else if (prevNode.previousSibling) {
|
||||
prevNode = prevNode.previousSibling;
|
||||
} else {
|
||||
prevNode = prevNode.previousSibling || prevNode.parentNode;
|
||||
prevNode = prevNode.parentNode;
|
||||
parentLookup.push(prevNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,8 +675,8 @@ function ngMessageDirectiveFactory(restrict) {
|
||||
// when we are destroying the node later.
|
||||
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
|
||||
|
||||
// in the event that the parent element is destroyed
|
||||
// by any other structural directive then it's time
|
||||
// in the event that the element or a parent element is destroyed
|
||||
// by another structural directive then it's time
|
||||
// to deregister the message from the controller
|
||||
currentElement.on('$destroy', function() {
|
||||
if (currentElement && currentElement.$$attachId === $$attachId) {
|
||||
|
||||
Vendored
+6
@@ -2480,6 +2480,12 @@ if (window.jasmine || window.mocha) {
|
||||
window.inject = angular.mock.inject = function() {
|
||||
var blockFns = Array.prototype.slice.call(arguments, 0);
|
||||
var errorForStack = new Error('Declaration Location');
|
||||
// IE10+ and PhanthomJS do not set stack trace information, until the error is thrown
|
||||
if (!errorForStack.stack) {
|
||||
try {
|
||||
throw errorForStack;
|
||||
} catch (e) {}
|
||||
}
|
||||
return isSpecRunning() ? workFn.call(currentSpec) : workFn;
|
||||
/////////////////////
|
||||
function workFn() {
|
||||
|
||||
@@ -112,19 +112,19 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should sanitize the html snippet by default', function() {
|
||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
||||
expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
|
||||
it('should inline raw snippet if bound to a trusted value', function() {
|
||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
||||
expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should escape snippet without any filter', function() {
|
||||
expect(element(by.css('#bind-default div')).getInnerHtml()).
|
||||
expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
@@ -133,11 +133,11 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
||||
it('should update', function() {
|
||||
element(by.model('snippet')).clear();
|
||||
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
|
||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
||||
expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
|
||||
toBe('new <b>text</b>');
|
||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
|
||||
expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
|
||||
'new <b onclick="alert(1)">text</b>');
|
||||
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
|
||||
expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
|
||||
"new <b onclick=\"alert(1)\">text</b>");
|
||||
});
|
||||
</file>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app>
|
||||
<body>
|
||||
<form>
|
||||
<input type="hidden" value="{{value}}" />
|
||||
<button ng-click="value = '{{ 7 * 6 }}'">Click me</button>
|
||||
</form>
|
||||
<script src="angular.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
describe('hidden thingy', function() {
|
||||
it('should pass', function() {
|
||||
|
||||
loadFixture('input-hidden');
|
||||
expect(element(by.css('input')).getAttribute('value')).toEqual('');
|
||||
|
||||
element(by.css('button')).click();
|
||||
expect(element(by.css('input')).getAttribute('value')).toEqual('{{ 7 * 6 }}');
|
||||
|
||||
loadFixture('sample');
|
||||
browser.driver.executeScript('history.back()');
|
||||
var expectedValue = browser.params.browser === 'safari' ? '{{ 7 * 6 }}' : '';
|
||||
expect(element(by.css('input')).getAttribute('value')).toEqual(expectedValue);
|
||||
});
|
||||
});
|
||||
@@ -7602,6 +7602,27 @@ describe('$compile', function() {
|
||||
|
||||
describe('img[srcset] sanitization', function() {
|
||||
|
||||
it('should not error if undefined', function() {
|
||||
var linked = false;
|
||||
module(function() {
|
||||
directive('setter', valueFn(function(scope, elem, attrs) {
|
||||
attrs.$set('srcset', 'http://example.com/');
|
||||
expect(attrs.srcset).toBe('http://example.com/');
|
||||
|
||||
attrs.$set('srcset', undefined);
|
||||
expect(attrs.srcset).toBeUndefined();
|
||||
|
||||
linked = true;
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<img setter></img>')($rootScope);
|
||||
|
||||
expect(linked).toBe(true);
|
||||
expect(element.attr('srcset')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT require trusted values for img srcset', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.png';
|
||||
|
||||
@@ -142,6 +142,16 @@ describe('ngBind*', function() {
|
||||
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
}));
|
||||
|
||||
it('should update html', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
$rootScope.html = 'hello';
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('hello');
|
||||
$rootScope.html = 'goodbye';
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('goodbye');
|
||||
}));
|
||||
|
||||
it('should one-time bind if the expression starts with two colons', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-bind-html="::html"></div>')($rootScope);
|
||||
$rootScope.html = '<div onclick="">hello</div>';
|
||||
@@ -176,7 +186,18 @@ describe('ngBind*', function() {
|
||||
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
}));
|
||||
|
||||
it('should watch the string value to avoid infinite recursion', inject(function($rootScope, $compile, $sce) {
|
||||
it('should update html', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
$rootScope.html = $sce.trustAsHtml('hello');
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('hello');
|
||||
$rootScope.html = $sce.trustAsHtml('goodbye');
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('goodbye');
|
||||
}));
|
||||
|
||||
it('should not cause infinite recursion for trustAsHtml object watches',
|
||||
inject(function($rootScope, $compile, $sce) {
|
||||
// Ref: https://github.com/angular/angular.js/issues/3932
|
||||
// If the binding is a function that creates a new value on every call via trustAs, we'll
|
||||
// trigger an infinite digest if we don't take care of it.
|
||||
@@ -188,6 +209,33 @@ describe('ngBind*', function() {
|
||||
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
}));
|
||||
|
||||
it('should handle custom $sce objects', function() {
|
||||
function MySafeHtml(val) { this.val = val; }
|
||||
|
||||
module(function($provide) {
|
||||
$provide.decorator('$sce', function($delegate) {
|
||||
$delegate.trustAsHtml = function(html) { return new MySafeHtml(html); };
|
||||
$delegate.getTrustedHtml = function(mySafeHtml) { return mySafeHtml.val; };
|
||||
$delegate.valueOf = function(v) { return v instanceof MySafeHtml ? v.val : v; };
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($rootScope, $compile, $sce) {
|
||||
// Ref: https://github.com/angular/angular.js/issues/14526
|
||||
// Previous code used toString for change detection, which fails for custom objects
|
||||
// that don't override toString.
|
||||
element = $compile('<div ng-bind-html="getHtml()"></div>')($rootScope);
|
||||
var html = 'hello';
|
||||
$rootScope.getHtml = function() { return $sce.trustAsHtml(html); };
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('hello');
|
||||
html = 'goodbye';
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('goodbye');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when $sanitize is available', function() {
|
||||
beforeEach(function() { module('ngSanitize'); });
|
||||
|
||||
|
||||
@@ -409,6 +409,22 @@ describe('ngClass', function() {
|
||||
expect(e2.hasClass('even')).toBeTruthy();
|
||||
expect(e2.hasClass('odd')).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should support mixed array/object variable with a mutating object',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-class="classVar"></div>')($rootScope);
|
||||
|
||||
$rootScope.classVar = [{orange: true}];
|
||||
$rootScope.$digest();
|
||||
expect(element).toHaveClass('orange');
|
||||
|
||||
$rootScope.classVar[0].orange = false;
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element).not.toHaveClass('orange');
|
||||
})
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
describe('ngClass animations', function() {
|
||||
|
||||
@@ -28,5 +28,10 @@ describe('ngSrcset', function() {
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toBe('http://example.com/image1.png 1x,unsafe:javascript:doEvilStuff() 2x');
|
||||
}));
|
||||
|
||||
it('should not throw an error if undefined', inject(function($rootScope, $compile) {
|
||||
element = $compile('<img ng-attr-srcset="{{undefined}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -35,7 +35,11 @@ describe('filters', function() {
|
||||
|
||||
it('should format according to different patterns', function() {
|
||||
pattern.gSize = 2;
|
||||
var num = formatNumber(1234567.89, pattern, ',', '.');
|
||||
var num = formatNumber(99, pattern, ',', '.');
|
||||
expect(num).toBe('99');
|
||||
num = formatNumber(888, pattern, ',', '.');
|
||||
expect(num).toBe('888');
|
||||
num = formatNumber(1234567.89, pattern, ',', '.');
|
||||
expect(num).toBe('12,34,567.89');
|
||||
num = formatNumber(1234.56, pattern, ',', '.');
|
||||
expect(num).toBe('1,234.56');
|
||||
|
||||
+19
-9
@@ -138,16 +138,26 @@ describe('$log', function() {
|
||||
describe('$log.error', function() {
|
||||
var e, $log, errorArgs;
|
||||
|
||||
beforeEach(function() {
|
||||
e = new Error('');
|
||||
e.message = undefined;
|
||||
e.sourceURL = undefined;
|
||||
e.line = undefined;
|
||||
e.stack = undefined;
|
||||
function TestError() {
|
||||
Error.prototype.constructor.apply(this, arguments);
|
||||
this.message = undefined;
|
||||
this.sourceURL = undefined;
|
||||
this.line = undefined;
|
||||
this.stack = undefined;
|
||||
}
|
||||
TestError.prototype = Object.create(Error.prototype);
|
||||
TestError.prototype.constructor = TestError;
|
||||
|
||||
$log = new $LogProvider().$get[1]({console:{error:function() {
|
||||
errorArgs = [].slice.call(arguments, 0);
|
||||
}}});
|
||||
beforeEach(function() {
|
||||
e = new TestError('');
|
||||
var mockWindow = {
|
||||
console: {
|
||||
error: function() {
|
||||
errorArgs = [].slice.call(arguments, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
$log = new $LogProvider().$get[1](mockWindow);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,39 @@ describe('$templateRequest', function() {
|
||||
}).not.toThrow();
|
||||
}));
|
||||
|
||||
it('should accept empty templates and refuse null or undefined templates in cache',
|
||||
inject(function($rootScope, $templateRequest, $templateCache, $sce) {
|
||||
|
||||
// Will throw on any template not in cache.
|
||||
spyOn($sce, 'getTrustedResourceUrl').andReturn(false);
|
||||
|
||||
expect(function() {
|
||||
$templateRequest('tpl.html'); // should go through $sce
|
||||
$rootScope.$digest();
|
||||
}).toThrow();
|
||||
|
||||
$templateCache.put('tpl.html'); // is a no-op, so $sce check as well.
|
||||
expect(function() {
|
||||
$templateRequest('tpl.html');
|
||||
$rootScope.$digest();
|
||||
}).toThrow();
|
||||
$templateCache.removeAll();
|
||||
|
||||
$templateCache.put('tpl.html', null); // makes no sense, but it's been added, so trust it.
|
||||
expect(function() {
|
||||
$templateRequest('tpl.html');
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
$templateCache.removeAll();
|
||||
|
||||
$templateCache.put('tpl.html', ''); // should work (empty template)
|
||||
expect(function() {
|
||||
$templateRequest('tpl.html');
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
$templateCache.removeAll();
|
||||
}));
|
||||
|
||||
it('should keep track of how many requests are going on',
|
||||
inject(function($rootScope, $templateRequest, $httpBackend) {
|
||||
|
||||
|
||||
@@ -395,6 +395,19 @@ describe("animations", function() {
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should not attempt to perform an animation on an empty jqLite collection',
|
||||
inject(function($rootScope, $animate) {
|
||||
|
||||
element.html('');
|
||||
var emptyNode = jqLite(element[0].firstChild);
|
||||
|
||||
$animate.addClass(emptyNode, 'some-class');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
})
|
||||
);
|
||||
|
||||
it('should perform the leave domOperation if a text node is used',
|
||||
inject(function($rootScope, $animate) {
|
||||
|
||||
@@ -1256,7 +1269,7 @@ describe("animations", function() {
|
||||
expect(element).not.toHaveClass('green');
|
||||
}));
|
||||
|
||||
it('should automatically cancel out class-based animations if the element already contains or doesn\' contain the applied classes',
|
||||
it('should automatically cancel out class-based animations if the element already contains or doesn\'t contain the applied classes',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
parent.append(element);
|
||||
@@ -1391,6 +1404,36 @@ describe("animations", function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not run animations if the document is unavailable', function() {
|
||||
var capturedAnimation;
|
||||
|
||||
module(function($provide) {
|
||||
$provide.value('$document', {});
|
||||
|
||||
$provide.factory('$$animation', function($$AnimateRunner) {
|
||||
return function(element, method, options) {
|
||||
capturedAnimation = arguments;
|
||||
return new $$AnimateRunner();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($animate, $rootScope, $rootElement, $document) {
|
||||
$animate.enabled(true);
|
||||
|
||||
var spy = jasmine.createSpy();
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
var runner = $animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.flush();
|
||||
|
||||
expect(capturedAnimation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('[ng-animate-children]', function() {
|
||||
var parent, element, child, capturedAnimation, captureLog;
|
||||
beforeEach(module(function($provide) {
|
||||
@@ -2137,5 +2180,169 @@ describe("animations", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when animations are skipped, disabled, or invalid', function() {
|
||||
|
||||
var overriddenAnimationRunner;
|
||||
var capturedAnimation;
|
||||
var capturedAnimationHistory;
|
||||
var defaultFakeAnimationRunner;
|
||||
var parent;
|
||||
var parent2;
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
overriddenAnimationRunner = null;
|
||||
capturedAnimation = null;
|
||||
capturedAnimationHistory = [];
|
||||
|
||||
$provide.value('$$animation', function() {
|
||||
capturedAnimationHistory.push(capturedAnimation = arguments);
|
||||
return overriddenAnimationRunner || defaultFakeAnimationRunner;
|
||||
});
|
||||
|
||||
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
|
||||
defaultFakeAnimationRunner = new $$AnimateRunner();
|
||||
$animate.enabled(true);
|
||||
|
||||
element = jqLite('<div class="element">element</div>');
|
||||
parent = jqLite('<div class="parent1">parent</div>');
|
||||
parent2 = jqLite('<div class="parent2">parent</div>');
|
||||
|
||||
$rootElement.append(parent);
|
||||
$rootElement.append(parent2);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
};
|
||||
}));
|
||||
|
||||
|
||||
it('should trigger all callbacks if a follow-up structural animation takes over a running animation',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
parent.append(element);
|
||||
var moveSpy = jasmine.createSpy();
|
||||
var leaveSpy = jasmine.createSpy();
|
||||
|
||||
$animate.on('move', parent2, moveSpy);
|
||||
$animate.on('leave', parent2, leaveSpy);
|
||||
|
||||
$animate.move(element, parent2);
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(moveSpy.calls.length).toBe(1);
|
||||
expect(moveSpy.mostRecentCall.args[1]).toBe('start');
|
||||
|
||||
$animate.leave(element);
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(moveSpy.calls.length).toBe(2);
|
||||
expect(moveSpy.mostRecentCall.args[1]).toBe('close');
|
||||
|
||||
expect(leaveSpy.calls.length).toBe(2);
|
||||
expect(leaveSpy.calls[0].args[1]).toBe('start');
|
||||
expect(leaveSpy.calls[1].args[1]).toBe('close');
|
||||
}));
|
||||
|
||||
|
||||
it('should not trigger callbacks for the previous structural animation if a follow-up structural animation takes over before the postDigest',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
var enterDone = jasmine.createSpy('enter animation done');
|
||||
|
||||
var enterSpy = jasmine.createSpy();
|
||||
var leaveSpy = jasmine.createSpy();
|
||||
|
||||
$animate.on('enter', parent, enterSpy);
|
||||
$animate.on('leave', parent, leaveSpy);
|
||||
|
||||
$animate.enter(element, parent).done(enterDone);
|
||||
expect(enterDone).not.toHaveBeenCalled();
|
||||
|
||||
var runner = $animate.leave(element);
|
||||
$animate.flush();
|
||||
expect(enterDone).toHaveBeenCalled();
|
||||
|
||||
expect(enterSpy).not.toHaveBeenCalled();
|
||||
expect(leaveSpy.calls.length).toBe(1);
|
||||
expect(leaveSpy.mostRecentCall.args[1]).toBe('start');
|
||||
|
||||
leaveSpy.reset();
|
||||
runner.end();
|
||||
$animate.flush();
|
||||
|
||||
expect(enterSpy).not.toHaveBeenCalled();
|
||||
expect(leaveSpy.calls.length).toBe(1);
|
||||
expect(leaveSpy.mostRecentCall.args[1]).toBe('close');
|
||||
}));
|
||||
|
||||
|
||||
it('should not trigger the callback if animations are disabled on the element',
|
||||
inject(function($animate, $rootScope, $rootElement, $document) {
|
||||
|
||||
var callbackTriggered = false;
|
||||
var spy = jasmine.createSpy('enter');
|
||||
$animate.on('enter', jqLite($document[0].body), spy);
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
$animate.enabled(element, false);
|
||||
var runner = $animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.flush(); // Flushes the animation frames for the callbacks
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
it('should not trigger the callbacks if the animation is skipped because there are no class-based animations and no structural animation',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
parent.append(element);
|
||||
var classSpy = jasmine.createSpy('classChange');
|
||||
$animate.on('addClass', element, classSpy);
|
||||
$animate.on('removeClass', element, classSpy);
|
||||
element.addClass('one three');
|
||||
|
||||
$animate.addClass(element, 'one');
|
||||
$animate.removeClass(element, 'four');
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect(classSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
|
||||
describe('because the document is hidden', function() {
|
||||
beforeEach(module(function($provide) {
|
||||
var doc = jqLite({
|
||||
body: document.body,
|
||||
hidden: true
|
||||
});
|
||||
$provide.value('$document', doc);
|
||||
}));
|
||||
|
||||
it('should trigger callbacks for an enter animation',
|
||||
inject(function($animate, $rootScope, $rootElement, $document) {
|
||||
|
||||
var callbackTriggered = false;
|
||||
var spy = jasmine.createSpy();
|
||||
$animate.on('enter', jqLite($document[0].body), spy);
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
var runner = $animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.flush(); // Flushes the animation frames for the callbacks
|
||||
|
||||
expect(spy.calls.length).toBe(2);
|
||||
expect(spy.calls[0].args[1]).toBe('start');
|
||||
expect(spy.calls[1].args[1]).toBe('close');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -445,6 +445,126 @@ describe('ngMessages', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngMessage nested nested inside elements', function() {
|
||||
|
||||
it('should not crash or leak memory when the messages are transcluded, the first message is ' +
|
||||
'visible, and ngMessages is removed by ngIf', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('messageWrap', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
scope: {
|
||||
col: '=col'
|
||||
},
|
||||
template: '<div ng-messages="col"><ng-transclude></ng-transclude></div>'
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($rootScope, $compile) {
|
||||
|
||||
element = $compile('<div><div ng-if="show"><div message-wrap col="col">' +
|
||||
' <div ng-message="a">A</div>' +
|
||||
' <div ng-message="b">B</div>' +
|
||||
'</div></div></div>')($rootScope);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.show = true;
|
||||
$rootScope.col = {
|
||||
a: true,
|
||||
b: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(messageChildren(element).length).toBe(1);
|
||||
expect(trim(element.text())).toEqual('A');
|
||||
|
||||
$rootScope.$apply('show = false');
|
||||
|
||||
expect(messageChildren(element).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not crash when the first of two nested messages is removed', function() {
|
||||
inject(function($rootScope, $compile) {
|
||||
|
||||
element = $compile(
|
||||
'<div ng-messages="col">' +
|
||||
'<div class="wrapper">' +
|
||||
'<div remove-me ng-message="a">A</div>' +
|
||||
'<div ng-message="b">B</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = {
|
||||
a: true,
|
||||
b: false
|
||||
};
|
||||
});
|
||||
|
||||
expect(messageChildren(element).length).toBe(1);
|
||||
expect(trim(element.text())).toEqual('A');
|
||||
|
||||
var ctrl = element.controller('ngMessages');
|
||||
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
|
||||
|
||||
var nodeA = element[0].querySelector('[ng-message="a"]');
|
||||
jqLite(nodeA).remove();
|
||||
$rootScope.$digest(); // The next digest triggers the error
|
||||
|
||||
// Make sure removing the element triggers the deregistration in ngMessages
|
||||
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: a');
|
||||
expect(messageChildren(element).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not crash, but show deeply nested messages correctly after a message ' +
|
||||
'has been removed', function() {
|
||||
inject(function($rootScope, $compile) {
|
||||
|
||||
element = $compile(
|
||||
'<div ng-messages="col" ng-messages-multiple>' +
|
||||
'<div class="another-wrapper">' +
|
||||
'<div ng-message="a">A</div>' +
|
||||
'<div class="wrapper">' +
|
||||
'<div ng-message="b">B</div>' +
|
||||
'<div ng-message="c">C</div>' +
|
||||
'</div>' +
|
||||
'<div ng-message="d">D</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.col = {
|
||||
a: true,
|
||||
b: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(messageChildren(element).length).toBe(2);
|
||||
expect(trim(element.text())).toEqual('AB');
|
||||
|
||||
var ctrl = element.controller('ngMessages');
|
||||
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
|
||||
|
||||
var nodeB = element[0].querySelector('[ng-message="b"]');
|
||||
jqLite(nodeB).remove();
|
||||
$rootScope.$digest(); // The next digest triggers the error
|
||||
|
||||
// Make sure removing the element triggers the deregistration in ngMessages
|
||||
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: b');
|
||||
expect(messageChildren(element).length).toBe(1);
|
||||
expect(trim(element.text())).toEqual('A');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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">' +
|
||||
@@ -651,6 +771,37 @@ describe('ngMessages', function() {
|
||||
expect(trim(element.text())).toEqual("C");
|
||||
}));
|
||||
|
||||
|
||||
it('should properly detect a previous message, even if it was registered later',
|
||||
inject(function($compile, $rootScope, $templateCache) {
|
||||
$templateCache.put('include.html', '<div ng-message="a">A</div>');
|
||||
var html =
|
||||
'<div ng-messages="items">' +
|
||||
'<div ng-include="\'include.html\'"></div>' +
|
||||
'<div ng-message="b">B</div>' +
|
||||
'<div ng-message="c">C</div>' +
|
||||
'</div>';
|
||||
|
||||
element = $compile(html)($rootScope);
|
||||
$rootScope.$apply('items = {b: true, c: true}');
|
||||
|
||||
expect(element.text()).toBe('B');
|
||||
|
||||
var ctrl = element.controller('ngMessages');
|
||||
var deregisterSpy = spyOn(ctrl, 'deregister').andCallThrough();
|
||||
|
||||
var nodeB = element[0].querySelector('[ng-message="b"]');
|
||||
jqLite(nodeB).remove();
|
||||
|
||||
// Make sure removing the element triggers the deregistration in ngMessages
|
||||
expect(trim(deregisterSpy.mostRecentCall.args[0].nodeValue)).toBe('ngMessage: b');
|
||||
|
||||
$rootScope.$apply('items.a = true');
|
||||
|
||||
expect(element.text()).toBe('A');
|
||||
})
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
describe('when multiple', function() {
|
||||
|
||||
Vendored
+46
@@ -919,6 +919,52 @@ describe('ngMock', function() {
|
||||
});
|
||||
}).toThrow('test message');
|
||||
}));
|
||||
|
||||
describe('error stack trace when called outside of spec context', function() {
|
||||
// - Chrome, Firefox, Edge, Opera give us the stack trace as soon as an Error is created
|
||||
// - IE10+, PhantomJS give us the stack trace only once the error is thrown
|
||||
// - IE9 does not provide stack traces
|
||||
var stackTraceSupported = (function() {
|
||||
var error = new Error();
|
||||
if (!error.stack) {
|
||||
try {
|
||||
throw error;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return !!error.stack;
|
||||
})();
|
||||
|
||||
function testCaller() {
|
||||
// Assigning to a local variable first, prevents Safari 10+ from inlining
|
||||
// the function call and having it appear as anonymous in the stack trace.
|
||||
var fn = inject(function() { throw new Error(); });
|
||||
return fn;
|
||||
}
|
||||
var throwErrorFromInjectCallback = testCaller();
|
||||
|
||||
if (stackTraceSupported) {
|
||||
describe('on browsers supporting stack traces', function() {
|
||||
it('should update thrown Error stack trace with inject call location', function() {
|
||||
try {
|
||||
throwErrorFromInjectCallback();
|
||||
} catch (e) {
|
||||
expect(e.stack).toMatch('testCaller');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
describe('on browsers not supporting stack traces', function() {
|
||||
it('should not add stack trace information to thrown Error', function() {
|
||||
try {
|
||||
throwErrorFromInjectCallback();
|
||||
} catch (e) {
|
||||
expect(e.stack).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user