Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
80fe41a88e | |||
af652a7c8b | |||
de36f8a3b9 | |||
b658fa9ea0 | |||
2a123463ac | |||
4f93ac8762 | |||
37ec5b9c1a | |||
612950bdb2 | |||
3804ad1d23 | |||
1b1f228525 | |||
19e9094275 | |||
8ff3ab0e6d | |||
9e8d740a96 | |||
3a3a100b27 | |||
f0575e014c | |||
ea7737ee11 | |||
fadaf1e01a | |||
c716532ff2 | |||
1c112ae66e | |||
193a0ae4a0 | |||
9ceb5d1afe | |||
d6a8b0b686 | |||
3e216dd4ad | |||
7b0aba4655 | |||
7c87c52c38 | |||
541de26f7e | |||
74cb575219 | |||
e90661aaee | |||
73034c75bd | |||
059085b943 | |||
8ab89153e0 | |||
82923d8314 | |||
6921b3d21d | |||
c0ef8b25a6 | |||
e845f3b226 | |||
9779f397b7 | |||
5bb47db887 | |||
343ee8a3a2 | |||
223b5eb367 | |||
7e639aac15 | |||
83dafd3054 | |||
e641636624 | |||
c409860a9f | |||
0101aa31d6 | |||
a5b4af0fdd | |||
d9420311ca | |||
774e1db87c | |||
109f0d16ef | |||
71567d1eee | |||
bb71acc172 | |||
e98d6f0912 | |||
1dbebb184f | |||
8882b86b54 | |||
0965636735 | |||
4d2901d480 | |||
a047124e1a | |||
09e2d20e22 | |||
e3bdf82c0d | |||
0614289608 | |||
7c344a4e49 | |||
250dbc4bc8 | |||
70bbdf55da | |||
41b8d95fa7 | |||
1eece5046d | |||
1ef3eeecbd | |||
94500e0fad | |||
dd53606f69 | |||
6c8b5dda87 | |||
458ccc1aff | |||
07cfd8c432 | |||
23bd0fbfc1 | |||
3d1e536143 | |||
c827097610 | |||
8d4aa82c04 | |||
14e97516cb | |||
bc47a8cc74 | |||
32cc6759ef | |||
d5f1419afe | |||
117fa79c7c | |||
777ba46837 | |||
f3d55068a8 | |||
7ed39ebaaf | |||
091f0a5aaa | |||
315606e02c | |||
5ea373d184 | |||
6e36bb7b20 | |||
3b2fb23805 | |||
bd2eecb4de | |||
3d351a4f5f | |||
5492fada21 | |||
fd4f9acbcf | |||
48528a86e1 | |||
80364def27 | |||
1803beb4d5 | |||
3bcba8a570 | |||
84542d8ae7 | |||
17cb3ec565 | |||
015878afe6 | |||
2af58622c1 | |||
7ffd10541d | |||
481b099d82 | |||
49c4b0fa92 | |||
b8b6b1d27a | |||
892b5ba950 | |||
bd15110c7d | |||
2250082fd7 | |||
87316c52db | |||
606b76d9bb | |||
3d0b1b8184 | |||
261fd16780 | |||
104cc42f6d |
240
.pullapprove.yml
240
.pullapprove.yml
@ -1,6 +1,24 @@
|
||||
# Configuration for pullapprove.com
|
||||
# See ownership spreadsheet:
|
||||
#
|
||||
# Approval access and primary role is determined by info in the project ownership spreadsheet:
|
||||
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
|
||||
#
|
||||
# === GitHub username to Full name map ===
|
||||
#
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# gkalpak - George Kalpakas
|
||||
# IgorMinar - Igor Minar
|
||||
# kara - Kara Erickson
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
# tbosch - Tobias Bosch
|
||||
# vicb - Victor Berchet
|
||||
# vikerman - Vikram Subramanian
|
||||
|
||||
version: 2
|
||||
|
||||
@ -9,24 +27,222 @@ group_defaults:
|
||||
reset_on_reopened:
|
||||
enabled: true
|
||||
approve_by_comment:
|
||||
enabled: true
|
||||
approve_regex: '^(Approved|:\+1:|LGTM)'
|
||||
enabled: false
|
||||
|
||||
groups:
|
||||
config:
|
||||
root:
|
||||
conditions:
|
||||
files:
|
||||
- "*.yml"
|
||||
- "*.json"
|
||||
teams:
|
||||
- repoowners
|
||||
include:
|
||||
- "*"
|
||||
exclude:
|
||||
- "angular.io/*"
|
||||
- "integration/*"
|
||||
- "modules/*"
|
||||
- "tools/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
public-api:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
build-and-ci:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*.yml"
|
||||
- "*.json"
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar #primary
|
||||
- mhevery
|
||||
|
||||
integration:
|
||||
conditions:
|
||||
files:
|
||||
- "integration/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- mhevery
|
||||
- tbosch
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
|
||||
core:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/core/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- mhevery
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler/animations:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/compiler/src/animation/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
compiler/i18n:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/compiler/src/i18n/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
compiler:
|
||||
conditions:
|
||||
files:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "modules/@angular/compiler/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- vicb
|
||||
- chuckjaz
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler-cli:
|
||||
conditions:
|
||||
files:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "modules/@angular/compiler-cli/*"
|
||||
teams:
|
||||
- compiler-owners
|
||||
- repoowners
|
||||
users:
|
||||
- alexeagle
|
||||
- chuckjaz
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
common:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/common/*"
|
||||
users:
|
||||
- pkozlowski-opensource #primary
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
forms:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/forms/*"
|
||||
users:
|
||||
- kara #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
http:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/http/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
- alxhub
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
language-service:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/language-service/*"
|
||||
users:
|
||||
- chuckjaz #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
router:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/router/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
upgrade:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/upgrade/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-browser:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-browser/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- vicb #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-server:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-server/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
- alxhub
|
||||
- vicb
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-webworker:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-webworker/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
- tbosch #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
|
||||
|
||||
benchpress:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/benchpress/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
angular.io:
|
||||
conditions:
|
||||
files:
|
||||
- "angular.io/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- robwormald
|
||||
- petebacondarwin
|
||||
- mhevery #fallback
|
||||
|
@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '6.6.0'
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
# firefox: "38.0"
|
||||
|
104
CHANGELOG.md
104
CHANGELOG.md
@ -1,3 +1,101 @@
|
||||
<a name="2.4.9"></a>
|
||||
## [2.4.9](https://github.com/angular/angular/compare/2.4.8...2.4.9) (2017-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **http:** Make ResponseOptionsArgs an interface ([b658fa9](https://github.com/angular/angular/commit/b658fa9)), closes [#13708](https://github.com/angular/angular/issues/13708)
|
||||
* **router:** improve robustness ([#14602](https://github.com/angular/angular/issues/14602)) ([2a12346](https://github.com/angular/angular/commit/2a12346))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix(router): do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([de36f8a](https://github.com/angular/angular/commit/de36f8a)), closes [#14681](https://github.com/angular/angular/issues/14681) [#14588](https://github.com/angular/angular/issues/14588)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.8"></a>
|
||||
## [2.4.8](https://github.com/angular/angular/compare/2.4.7...2.4.8) (2017-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **forms:** getRawValue should correctly work with nested FormGroups/Arrays ([#12964](https://github.com/angular/angular/issues/12964)) ([ea7737e](https://github.com/angular/angular/commit/ea7737e)), closes [#12963](https://github.com/angular/angular/issues/12963)
|
||||
* **http:** REVERT: remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([9ceb5d1](https://github.com/angular/angular/commit/9ceb5d1))
|
||||
* **platform-browser:** should only add styles with native encapsulation in shadow DOM ([#14313](https://github.com/angular/angular/issues/14313)) ([fadaf1e](https://github.com/angular/angular/commit/fadaf1e)), closes [#7887](https://github.com/angular/angular/issues/7887)
|
||||
* **router:** do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([541de26](https://github.com/angular/angular/commit/541de26)), closes [#12162](https://github.com/angular/angular/issues/12162)
|
||||
* **upgrade:** correctly project content on downgraded components with structural directives ([#14274](https://github.com/angular/angular/issues/14274)) ([74cb575](https://github.com/angular/angular/commit/74cb575)), closes [#14260](https://github.com/angular/angular/issues/14260)
|
||||
* **upgrade:** pass correct values to `ngOnChanges` for interpolation bindings ([#14400](https://github.com/angular/angular/issues/14400)) ([7c87c52](https://github.com/angular/angular/commit/7c87c52))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.7"></a>
|
||||
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **upgrade:** allow non-element selectors for downgraded components ([#14291](https://github.com/angular/angular/issues/14291)) ([5bb47db](https://github.com/angular/angular/commit/5bb47db))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.6"></a>
|
||||
## [2.4.6](https://github.com/angular/angular/compare/2.4.5...2.4.6) (2017-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add PopStateEvent interface ([#13400](https://github.com/angular/angular/issues/13400)) ([71567d1](https://github.com/angular/angular/commit/71567d1)), closes [#13378](https://github.com/angular/angular/issues/13378)
|
||||
* **common:** DatePipe does't throw for NaN ([#14117](https://github.com/angular/angular/issues/14117)) ([32cc675](https://github.com/angular/angular/commit/32cc675)), closes [#14103](https://github.com/angular/angular/issues/14103)
|
||||
* **common:** DatePipe parses input string if it's not a valid date in browser ([#13895](https://github.com/angular/angular/issues/13895)) ([e641636](https://github.com/angular/angular/commit/e641636)), closes [#12334](https://github.com/angular/angular/issues/12334) [#13874](https://github.com/angular/angular/issues/13874)
|
||||
* **common:** introduce isObservable method ([#14067](https://github.com/angular/angular/issues/14067)) ([109f0d1](https://github.com/angular/angular/commit/109f0d1)), closes [#8848](https://github.com/angular/angular/issues/8848)
|
||||
* **compiler:** allow empty translations for attributes ([#14085](https://github.com/angular/angular/issues/14085)) ([f3d5506](https://github.com/angular/angular/commit/f3d5506)), closes [#13897](https://github.com/angular/angular/issues/13897)
|
||||
* **core:** add bootstrapped modules into platform modules list ([#13740](https://github.com/angular/angular/issues/13740)) ([250dbc4](https://github.com/angular/angular/commit/250dbc4)), closes [#12015](https://github.com/angular/angular/issues/12015)
|
||||
* **core:** ViewContainerRef.indexOf should not throw error when empty ([#13220](https://github.com/angular/angular/issues/13220)) ([41b8d95](https://github.com/angular/angular/commit/41b8d95))
|
||||
* **forms:** show a blank line when nothing is selected in IE or Edge ([#13903](https://github.com/angular/angular/issues/13903)) ([09e2d20](https://github.com/angular/angular/commit/09e2d20)), closes [#10010](https://github.com/angular/angular/issues/10010)
|
||||
* **forms:** verify functions passed into async validators returns Observable or Promise ([#14053](https://github.com/angular/angular/issues/14053)) ([774e1db](https://github.com/angular/angular/commit/774e1db))
|
||||
* ngModel should use rxjs/symbol/observable to detect observable ([#14236](https://github.com/angular/angular/issues/14236)) ([7e639aa](https://github.com/angular/angular/commit/7e639aa))
|
||||
* **http:** remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([1eece50](https://github.com/angular/angular/commit/1eece50))
|
||||
* **i18n:** parse ICU messages while normalizing templates ([#14153](https://github.com/angular/angular/issues/14153)) ([8d4aa82](https://github.com/angular/angular/commit/8d4aa82))
|
||||
* **language-service:** do not crash when Angular cannot be located ([#14123](https://github.com/angular/angular/issues/14123)) ([a5b4af0](https://github.com/angular/angular/commit/a5b4af0)), closes [#14122](https://github.com/angular/angular/issues/14122)
|
||||
* **platform-browser:** remove style nodes on destroy ([#13744](https://github.com/angular/angular/issues/13744)) ([0614289](https://github.com/angular/angular/commit/0614289)), closes [#11746](https://github.com/angular/angular/issues/11746)
|
||||
* **router:** fix CanActivate redirect to the root on initial load ([#13929](https://github.com/angular/angular/issues/13929)) ([a047124](https://github.com/angular/angular/commit/a047124)), closes [#13530](https://github.com/angular/angular/issues/13530)
|
||||
* **router:** should find guard provided in a lazy loaded module ([#13989](https://github.com/angular/angular/issues/13989)) ([0965636](https://github.com/angular/angular/commit/0965636)), closes [#12275](https://github.com/angular/angular/issues/12275)
|
||||
* **router:** should allow navigation from root component in ngOnInit hook ([#13932](https://github.com/angular/angular/issues/13932)) ([4d2901d](https://github.com/angular/angular/commit/4d2901d)), closes [#13795](https://github.com/angular/angular/issues/13795)
|
||||
* **testing:** async/fakeAsync/inject/withModule helpers should pass through context to callback functions ([#13718](https://github.com/angular/angular/issues/13718)) ([70bbdf5](https://github.com/angular/angular/commit/70bbdf5))
|
||||
* **upgrade:** detect async downgrade component changes ([#14039](https://github.com/angular/angular/issues/14039)) ([117fa79](https://github.com/angular/angular/commit/117fa79)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.5"></a>
|
||||
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
|
||||
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
|
||||
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
|
||||
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.4"></a>
|
||||
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
|
||||
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
|
||||
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
|
||||
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
|
||||
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
|
||||
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
|
||||
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.3"></a>
|
||||
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
|
||||
|
||||
@ -26,7 +124,7 @@
|
||||
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
|
||||
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
|
||||
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
|
||||
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
|
||||
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
|
||||
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
|
||||
@ -118,10 +216,10 @@
|
||||
|
||||
### Note ###
|
||||
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
|
||||
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
|
||||
|
||||
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
|
||||
|
27
COMMITTER.md
27
COMMITTER.md
@ -6,29 +6,16 @@ for details about how we maintain a linear commit history, and the rules for com
|
||||
As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request.
|
||||
Someone with committer access will do the rest.
|
||||
|
||||
## The `PR: merge` label and `presubmit-*` branches
|
||||
# Change approvals
|
||||
|
||||
We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for
|
||||
Angular committers and also prevent breakages on master.
|
||||
Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
|
||||
|
||||
When a PR has `pr_state: LGTM` and is ready to merge, you should add the `pr_action: merge` label.
|
||||
Currently (late 2015), we need to ensure that each PR will cleanly merge into the Google-internal version control,
|
||||
so the caretaker reviews the changes manually.
|
||||
|
||||
After this review, the caretaker adds `zomg_admin: do_merge` which is restricted to admins only.
|
||||
A robot running as [mary-poppins](https://github.com/mary-poppins)
|
||||
is notified that the label was added by an authorized person,
|
||||
and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`.
|
||||
# Merging
|
||||
|
||||
(Note: if the automation fails, committers can instead push the commits to a branch following this naming scheme.)
|
||||
Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with "PR: merge" label.
|
||||
This signals to the caretaker that the PR should be merged.
|
||||
|
||||
When a Travis build succeeds for a presubmit branch named following the convention,
|
||||
Travis will re-base the commits, merge to master, and close the PR automatically.
|
||||
# Who is the Caretaker?
|
||||
|
||||
Finally, after merge `mary-poppins` removes the presubmit branch.
|
||||
|
||||
## Administration
|
||||
|
||||
The list of users who can trigger a merge by adding the `zomg_admin: do_merge` label is stored in our appengine app datastore.
|
||||
Edit the contents of the [CoreTeamMember Table](
|
||||
https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember)
|
||||
See [this explanation](https://twitter.com/IgorMinar/status/799365744806854656).
|
||||
|
@ -71,7 +71,13 @@ particular `gulp` and `protractor` commands. If you prefer, you can drop this pa
|
||||
Since global installs can become stale, and required versions can vary by project, we avoid their
|
||||
use in these instructions.
|
||||
|
||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
*Option 2*: globally installing the package `npm-run` by running `npm install -g npm-run`
|
||||
(you might need to prefix this command with `sudo`). You will then be able to run locally installed
|
||||
package scripts by invoking: e.g., `npm-run gulp build`
|
||||
(see [npm-run project page](https://github.com/timoxley/npm-run) for more details).
|
||||
|
||||
|
||||
*Option 3*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Installing Bower Modules
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014-2016 Google, Inc. http://angular.io
|
||||
Copyright (c) 2014-2017 Google, Inc. http://angular.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,10 +1,10 @@
|
||||
machine:
|
||||
node:
|
||||
version: 5.4.1
|
||||
version: 6.9.5
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm@3.6.0
|
||||
- npm install -g npm@3.10.7
|
||||
|
||||
test:
|
||||
override:
|
||||
|
@ -19,7 +19,7 @@ One intentional omission from this list is `@angular/compiler`, which is current
|
||||
|
||||
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
|
||||
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature.
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, will be covered by these or similar guarantees in the future as they mature.
|
||||
|
||||
Within the supported packages, we provide guarantees for:
|
||||
|
||||
@ -37,4 +37,4 @@ We explicitly don't consider the following to be our public API surface:
|
||||
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
|
||||
|
||||
|
||||
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
|
||||
Our peer dependencies (e.g. TypeScript, Zone.js, or RxJS) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
|
||||
|
@ -11,8 +11,8 @@
|
||||
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
|
||||
// This is to ensure that we catch env issues before we error while requiring other dependencies.
|
||||
require('./tools/check-environment')({
|
||||
requiredNpmVersion: '>=3.5.3 <4.0.0',
|
||||
requiredNodeVersion: '>=5.4.1 <7.0.0',
|
||||
requiredNpmVersion: '>=3.10.7 <4.0.0',
|
||||
requiredNodeVersion: '>=6.9.5 <7.0.0',
|
||||
});
|
||||
|
||||
const gulp = require('gulp');
|
||||
|
@ -103,6 +103,7 @@ export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
const isANumber: boolean = !isNaN(Number(value));
|
||||
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,12 @@ import {EventEmitter, Injectable} from '@angular/core';
|
||||
|
||||
import {LocationStrategy} from './location_strategy';
|
||||
|
||||
/** @experimental */
|
||||
export interface PopStateEvent {
|
||||
pop?: boolean;
|
||||
type?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
|
||||
@ -122,7 +128,7 @@ export class Location {
|
||||
* Subscribe to the platform's `popState` events.
|
||||
*/
|
||||
subscribe(
|
||||
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
|
||||
onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
|
||||
onReturn: () => void = null): Object {
|
||||
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
||||
}
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
|
||||
import {ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, WrappedValue} from '@angular/core';
|
||||
import {EventEmitter, Observable} from '../facade/async';
|
||||
import {isPromise} from '../private_import_core';
|
||||
import {isObservable, isPromise} from '../private_import_core';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
interface SubscriptionStrategy {
|
||||
@ -66,7 +66,7 @@ const _observableStrategy = new ObservableStrategy();
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'async', pure: false})
|
||||
export class AsyncPipe implements OnDestroy {
|
||||
export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||
private _latestValue: Object = null;
|
||||
private _latestReturnedValue: Object = null;
|
||||
|
||||
@ -116,7 +116,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
return _promiseStrategy;
|
||||
}
|
||||
|
||||
if ((<any>obj).subscribe) {
|
||||
if (isObservable(obj)) {
|
||||
return _observableStrategy;
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
this._obj = null;
|
||||
}
|
||||
|
||||
private _updateLatestValue(async: any, value: Object) {
|
||||
private _updateLatestValue(async: any, value: Object): void {
|
||||
if (async === this._obj) {
|
||||
this._latestValue = value;
|
||||
this._ref.markForCheck();
|
||||
|
@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
import {NumberWrapper, isDate} from '../facade/lang';
|
||||
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {DateFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
const ISO8601_DATE_REGEX =
|
||||
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
|
||||
// 1 2 3 4 5 6 7 8 9 10 11
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* Where:
|
||||
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime).
|
||||
* - `format` indicates which date/time components to include. The format can be predifined as
|
||||
* - `format` indicates which date/time components to include. The format can be predefined as
|
||||
* shown below or custom as shown in the table.
|
||||
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
|
||||
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
|
||||
@ -103,7 +103,7 @@ export class DatePipe implements PipeTransform {
|
||||
transform(value: any, pattern: string = 'mediumDate'): string {
|
||||
let date: Date;
|
||||
|
||||
if (isBlank(value)) return null;
|
||||
if (isBlank(value) || value !== value) return null;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
@ -130,7 +130,12 @@ export class DatePipe implements PipeTransform {
|
||||
}
|
||||
|
||||
if (!isDate(date)) {
|
||||
throw new InvalidPipeArgumentError(DatePipe, value);
|
||||
let match: RegExpMatchArray;
|
||||
if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
|
||||
date = isoStringToDate(match);
|
||||
} else {
|
||||
throw new InvalidPipeArgumentError(DatePipe, value);
|
||||
}
|
||||
}
|
||||
|
||||
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
|
||||
@ -140,3 +145,31 @@ export class DatePipe implements PipeTransform {
|
||||
function isBlank(obj: any): boolean {
|
||||
return obj == null || obj === '';
|
||||
}
|
||||
|
||||
function isDate(obj: any): obj is Date {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
function isoStringToDate(match: RegExpMatchArray): Date {
|
||||
const date = new Date(0);
|
||||
let tzHour = 0;
|
||||
let tzMin = 0;
|
||||
const dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear;
|
||||
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
|
||||
|
||||
if (match[9]) {
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(match[9] + match[11]);
|
||||
}
|
||||
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
const h = toInt(match[4] || '0') - tzHour;
|
||||
const m = toInt(match[5] || '0') - tzMin;
|
||||
const s = toInt(match[6] || '0');
|
||||
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
}
|
||||
|
||||
function toInt(str: string): number {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
@ -9,3 +9,4 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export const isPromise: typeof r.isPromise = r.isPromise;
|
||||
export const isObservable: typeof r.isObservable = r.isObservable;
|
||||
|
@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
describe('ngPlural', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||
@ -47,6 +47,22 @@ export function main() {
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
it('should display the template according to the exact numeric value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().switchValue = 0;
|
||||
detectChangesAndExpectText('you have no messages.');
|
||||
|
||||
getComponent().switchValue = 1;
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||
|
@ -53,11 +53,13 @@ export function main() {
|
||||
|
||||
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null));
|
||||
|
||||
it('should return null for NaN', () => expect(pipe.transform(Number.NaN)).toEqual(null));
|
||||
|
||||
it('should support ISO string without time',
|
||||
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform({})).toThrowError(); });
|
||||
() => expect(() => pipe.transform({})).toThrowError(/Invalid argument/));
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
@ -188,8 +190,14 @@ export function main() {
|
||||
|
||||
});
|
||||
|
||||
it('should format invalid in IE ISO date',
|
||||
() => expect(pipe.transform('2017-01-11T09:25:14.014-0500')).toEqual('Jan 11, 2017'));
|
||||
|
||||
it('should format invalid in Safari ISO date',
|
||||
() => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
|
||||
|
||||
it('should remove bidi control characters',
|
||||
() => { expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10); });
|
||||
() => expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "0.5.1",
|
||||
"@angular/tsc-wrapped": "0.5.2",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -36,29 +36,6 @@ export class CodeGenerator {
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
@ -66,7 +43,7 @@ export class CodeGenerator {
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
|
@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
return !excludeRegex.test(filePath);
|
||||
}
|
||||
|
||||
calculateEmitPath(filePath: string): string {
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompilerHostContextAdapter {
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
|
||||
import {ReflectorReader} from '../private_import_core';
|
||||
import {SyntaxError} from '../util';
|
||||
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
@ -538,7 +539,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (e.fileName) {
|
||||
throw positionalError(message, e.fileName, e.line, e.column);
|
||||
}
|
||||
throw new Error(message);
|
||||
throw new SyntaxError(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,7 +273,12 @@ export class StaticSymbolResolver {
|
||||
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
|
||||
const filePath = this.resolveModule(module, containingFile);
|
||||
if (!filePath) {
|
||||
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
|
||||
this.reportError(
|
||||
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
|
||||
containingFile
|
||||
} `: ''}`),
|
||||
null);
|
||||
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
|
||||
}
|
||||
return this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
@ -15,10 +15,6 @@ import {LifecycleHooks, reflector} from './private_import_core';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
// group 0: "[prop] or (event) or @trigger"
|
||||
// group 1: "prop" from "[prop]"
|
||||
// group 2: "event" from "(event)"
|
||||
|
@ -11,10 +11,6 @@ import {ViewEncapsulation, isDevMode} from '@angular/core';
|
||||
import {CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {Identifiers, createIdentifier} from './identifiers';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
export class CompilerConfig {
|
||||
public renderTypes: RenderTypes;
|
||||
public defaultEncapsulation: ViewEncapsulation;
|
||||
@ -52,12 +48,12 @@ export class CompilerConfig {
|
||||
* to help tree shaking.
|
||||
*/
|
||||
export abstract class RenderTypes {
|
||||
get renderer(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderText(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderElement(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderComment(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderNode(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
abstract get renderer(): CompileIdentifierMetadata;
|
||||
abstract get renderText(): CompileIdentifierMetadata;
|
||||
abstract get renderElement(): CompileIdentifierMetadata;
|
||||
abstract get renderComment(): CompileIdentifierMetadata;
|
||||
abstract get renderNode(): CompileIdentifierMetadata;
|
||||
abstract get renderEvent(): CompileIdentifierMetadata;
|
||||
}
|
||||
|
||||
export class DefaultRenderTypes implements RenderTypes {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ViewEncapsulation} from '@angular/core';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
@ -102,11 +102,12 @@ export class DirectiveNormalizer {
|
||||
templateAbsUrl: string): CompileTemplateMetadata {
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
||||
template, stringify(prenomData.componentType), true, interpolationConfig);
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
|
||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||
styles: prenomData.styles,
|
||||
styleUrls: prenomData.styleUrls,
|
||||
@ -228,9 +229,13 @@ class TemplatePreparseVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
|
||||
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
|
||||
html.visitAll(this, ast.expression);
|
||||
}
|
||||
|
||||
visitComment(ast: html.Comment, context: any): any { return null; }
|
||||
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
||||
visitText(ast: html.Text, context: any): any { return null; }
|
||||
visitExpansion(ast: html.Expansion, context: any): any { return null; }
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class Extractor {
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
const {files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
|
@ -325,6 +325,7 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
// Translates the given message given the `TranslationBundle`
|
||||
// This is used for translating elements / blocks - see `_translateAttributes` for attributes
|
||||
// no-op when called in extraction mode (returns [])
|
||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||
if (message && this._mode === _VisitorMode.Merge) {
|
||||
@ -366,7 +367,9 @@ class _Visitor implements html.Visitor {
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
if (nodes.length == 0) {
|
||||
translatedAttributes.push(new html.Attribute(attr.name, '', attr.sourceSpan));
|
||||
} else if (nodes[0] instanceof html.Text) {
|
||||
const value = (nodes[0] as html.Text).value;
|
||||
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
|
||||
} else {
|
||||
@ -401,7 +404,7 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the start of a section, see `_endSection`
|
||||
* Marks the start of a section, see `_closeTranslatableSection`
|
||||
*/
|
||||
private _openTranslatableSection(node: html.Node): void {
|
||||
if (this._isInTranslatableSection) {
|
||||
|
@ -111,8 +111,14 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
||||
if (!seen) {
|
||||
this._placeHolderNameCounts[base] = 1;
|
||||
return base;
|
||||
}
|
||||
|
||||
const id = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = id + 1;
|
||||
return `${base}_${id}`;
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,26 @@
|
||||
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
export interface Serializer {
|
||||
write(messages: i18n.Message[]): string;
|
||||
export abstract class Serializer {
|
||||
abstract write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
digest(message: i18n.Message): string;
|
||||
abstract digest(message: i18n.Message): string;
|
||||
|
||||
// Creates a name mapper, see `PlaceholderMapper`
|
||||
// Returning `null` means that no name mapping is used.
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
|
||||
* back.
|
||||
*
|
||||
* It should be used for serialization format that put constraints on the placeholder names.
|
||||
*/
|
||||
export interface PlaceholderMapper {
|
||||
toPublicName(internalName: string): string;
|
||||
|
||||
toInternalName(publicName: string): string;
|
||||
}
|
@ -27,7 +27,7 @@ const _UNIT_TAG = 'trans-unit';
|
||||
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
|
||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
export class Xliff implements Serializer {
|
||||
export class Xliff extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _MESSAGES_TAG = 'messagebundle';
|
||||
@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
export class Xmb extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const exampleVisitor = new ExampleVisitor();
|
||||
const visitor = new _Visitor();
|
||||
@ -51,6 +51,8 @@ export class Xmb implements Serializer {
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const mapper = this.createNameMapper(message);
|
||||
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@ -62,7 +64,8 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
rootNode.children.push(
|
||||
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
|
||||
new xml.CR(2),
|
||||
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
|
||||
});
|
||||
|
||||
rootNode.children.push(new xml.CR());
|
||||
@ -82,22 +85,29 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
||||
visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [new xml.Text(text.value)];
|
||||
}
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
||||
visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
|
||||
const nodes: xml.Node[] = [];
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
|
||||
});
|
||||
|
||||
nodes.push(new xml.Text(`}`));
|
||||
@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
|
||||
let name = ctx.mapper.toPublicName(ph.startName);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
|
||||
if (ph.isVoid) {
|
||||
// void tags have no children nor closing tags
|
||||
return [startTagPh];
|
||||
}
|
||||
|
||||
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
|
||||
name = ctx.mapper.toPublicName(ph.closeName);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
|
||||
|
||||
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
|
||||
return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this, ctx)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
|
||||
visitDeclaration(decl: xml.Declaration): void {}
|
||||
visitDoctype(doctype: xml.Doctype): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
|
||||
*
|
||||
* Because such restrictions do not exist on placeholder names generated locally, the
|
||||
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
|
||||
* serialized and back from XTB to internal names when an XTB is loaded.
|
||||
*/
|
||||
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
|
||||
private internalToXmb: {[k: string]: string} = {};
|
||||
private xmbToNextId: {[k: string]: number} = {};
|
||||
private xmbToInternal: {[k: string]: string} = {};
|
||||
|
||||
// create a mapping from the message
|
||||
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
|
||||
|
||||
toPublicName(internalName: string): string {
|
||||
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
|
||||
null;
|
||||
}
|
||||
|
||||
toInternalName(publicName: string): string {
|
||||
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, ctx?: any): any { return null; }
|
||||
|
||||
visitContainer(container: i18n.Container, ctx?: any): any {
|
||||
container.children.forEach(child => child.visit(this));
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, ctx?: any): any {
|
||||
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
|
||||
this.addPlaceholder(ph.startName);
|
||||
ph.children.forEach(child => child.visit(this));
|
||||
this.addPlaceholder(ph.closeName);
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
// XMB placeholders could only contains A-Z, 0-9 and _
|
||||
private addPlaceholder(internalName: string): void {
|
||||
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
||||
|
||||
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
|
||||
// Create a new XMB when it has already been used
|
||||
const nextId = this.xmbToNextId[xmbName];
|
||||
this.xmbToNextId[xmbName] = nextId + 1;
|
||||
xmbName = `${xmbName}_${nextId}`;
|
||||
} else {
|
||||
this.xmbToNextId[xmbName] = 1;
|
||||
}
|
||||
|
||||
this.internalToXmb[internalName] = xmbName;
|
||||
this.xmbToInternal[xmbName] = internalName;
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import {XmbPlaceholderMapper, digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
export class Xtb extends Serializer {
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
@ -43,6 +43,10 @@ export class Xtb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
|
@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
@ -21,16 +21,20 @@ export class TranslationBundle {
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
public digest: (m: i18n.Message) => string,
|
||||
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
|
||||
}
|
||||
|
||||
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
|
||||
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn);
|
||||
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
|
||||
}
|
||||
|
||||
// Returns the translation as HTML nodes from the given source message.
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
@ -46,15 +50,17 @@ export class TranslationBundle {
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
private _mapper: (name: string) => string;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
private _digest: (m: i18n.Message) => string,
|
||||
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._contextStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
const phName = this._mapper(ph.name);
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
@ -105,14 +111,26 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
/**
|
||||
* Convert a source message to a translated text string:
|
||||
* - text nodes are replaced with their translation,
|
||||
* - placeholders are replaced with their content,
|
||||
* - ICU nodes are converted to ICU expressions.
|
||||
*/
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
|
||||
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
|
||||
this._srcMsg = srcMsg;
|
||||
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
|
||||
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
const context = this._contextStack.pop();
|
||||
this._srcMsg = context.msg;
|
||||
this._mapper = context.mapper;
|
||||
return text;
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ export class JitCompiler implements Compiler {
|
||||
// Note: the loadingPromise for a module only includes the loading of the exported directives
|
||||
// of imported modules.
|
||||
// However, for runtime compilation, we want to transitively compile all modules,
|
||||
// so we also need to call loadNgModuleMetadata for all nested modules.
|
||||
// so we also need to call loadNgModuleDirectiveAndPipeMetadata for all nested modules.
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
localModuleMeta.reference, isSync));
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompilerConfig} from '../config';
|
||||
@ -40,6 +40,8 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
|
||||
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
|
||||
};
|
||||
|
||||
const baseHtmlParser = new OpaqueToken('HtmlParser');
|
||||
|
||||
/**
|
||||
* A set of providers that provide `JitCompiler` and its dependencies to use for
|
||||
* template compilation.
|
||||
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
Console,
|
||||
Lexer,
|
||||
Parser,
|
||||
HtmlParser,
|
||||
{
|
||||
provide: baseHtmlParser,
|
||||
useClass: HtmlParser,
|
||||
},
|
||||
{
|
||||
provide: i18n.I18NHtmlParser,
|
||||
useFactory: (parser: HtmlParser, translations: string, format: string) =>
|
||||
new i18n.I18NHtmlParser(parser, translations, format),
|
||||
deps: [
|
||||
HtmlParser,
|
||||
baseHtmlParser,
|
||||
[new Optional(), new Inject(TRANSLATIONS)],
|
||||
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: HtmlParser,
|
||||
useExisting: i18n.I18NHtmlParser,
|
||||
},
|
||||
TemplateParser,
|
||||
DirectiveNormalizer,
|
||||
CompileMetadataResolver,
|
||||
|
@ -294,14 +294,14 @@ export class CompileMetadataResolver {
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given directive.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
|
||||
*/
|
||||
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
const dirMeta = this._directiveCache.get(directiveType);
|
||||
if (!dirMeta) {
|
||||
this._reportError(
|
||||
new SyntaxError(
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
|
||||
directiveType);
|
||||
}
|
||||
return dirMeta;
|
||||
@ -648,14 +648,14 @@ export class CompileMetadataResolver {
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given pipe.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
|
||||
*/
|
||||
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
const pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
this._reportError(
|
||||
new SyntaxError(
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
|
||||
pipeType);
|
||||
}
|
||||
return pipeMeta;
|
||||
|
@ -48,6 +48,51 @@ export class ParseLocation {
|
||||
}
|
||||
return new ParseLocation(this.file, offset, line, col);
|
||||
}
|
||||
|
||||
// Return the source around the location
|
||||
// Up to `maxChars` or `maxLines` on each side of the location
|
||||
getContext(maxChars: number, maxLines: number): {before: string, after: string} {
|
||||
const content = this.file.content;
|
||||
let startOffset = this.offset;
|
||||
|
||||
if (isPresent(startOffset)) {
|
||||
if (startOffset > content.length - 1) {
|
||||
startOffset = content.length - 1;
|
||||
}
|
||||
let endOffset = startOffset;
|
||||
let ctxChars = 0;
|
||||
let ctxLines = 0;
|
||||
|
||||
while (ctxChars < maxChars && startOffset > 0) {
|
||||
startOffset--;
|
||||
ctxChars++;
|
||||
if (content[startOffset] == '\n') {
|
||||
if (++ctxLines == maxLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctxChars = 0;
|
||||
ctxLines = 0;
|
||||
while (ctxChars < maxChars && endOffset < content.length - 1) {
|
||||
endOffset++;
|
||||
ctxChars++;
|
||||
if (content[endOffset] == '\n') {
|
||||
if (++ctxLines == maxLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
before: content.substring(startOffset, this.offset),
|
||||
after: content.substring(this.offset, endOffset + 1),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseSourceFile {
|
||||
@ -74,47 +119,9 @@ export class ParseError {
|
||||
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
|
||||
|
||||
toString(): string {
|
||||
const source = this.span.start.file.content;
|
||||
let ctxStart = this.span.start.offset;
|
||||
let contextStr = '';
|
||||
let details = '';
|
||||
if (isPresent(ctxStart)) {
|
||||
if (ctxStart > source.length - 1) {
|
||||
ctxStart = source.length - 1;
|
||||
}
|
||||
let ctxEnd = ctxStart;
|
||||
let ctxLen = 0;
|
||||
let ctxLines = 0;
|
||||
|
||||
while (ctxLen < 100 && ctxStart > 0) {
|
||||
ctxStart--;
|
||||
ctxLen++;
|
||||
if (source[ctxStart] == '\n') {
|
||||
if (++ctxLines == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctxLen = 0;
|
||||
ctxLines = 0;
|
||||
while (ctxLen < 100 && ctxEnd < source.length - 1) {
|
||||
ctxEnd++;
|
||||
ctxLen++;
|
||||
if (source[ctxEnd] == '\n') {
|
||||
if (++ctxLines == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
|
||||
source.substring(this.span.start.offset, ctxEnd + 1);
|
||||
contextStr = ` ("${context}")`;
|
||||
}
|
||||
if (this.span.details) {
|
||||
details = `, ${this.span.details}`;
|
||||
}
|
||||
const ctx = this.span.start.getContext(100, 3);
|
||||
const contextStr = ctx ? ` ("${ctx.before}[ERROR ->]${ctx.after}")` : '';
|
||||
const details = this.span.details ? `, ${this.span.details}` : '';
|
||||
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,11 @@
|
||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'g');
|
||||
|
@ -404,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
||||
o.literal(view.component.template.ngContentSelectors.length),
|
||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||
view.styles,
|
||||
o.literalMap(view.animations.map(
|
||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
||||
o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||
null, true),
|
||||
]))
|
||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||
}
|
||||
|
2
modules/@angular/compiler/test/aot/README.md
Normal file
2
modules/@angular/compiler/test/aot/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
Tests in this directory are excluded from running in the browser and only running
|
||||
in node.
|
191
modules/@angular/compiler/test/aot/compiler_spec.ts
Normal file
191
modules/@angular/compiler/test/aot/compiler_spec.ts
Normal file
@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, createAotCompiler} from '@angular/compiler';
|
||||
import {async} from '@angular/core/testing';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReflectionCapabilities, reflector} from './private_import_core';
|
||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, settings} from './test_util';
|
||||
|
||||
const DTS = /\.d\.ts$/;
|
||||
|
||||
// These are the files that contain the well known annotations.
|
||||
const CORE_FILES = [
|
||||
'@angular/core/src/metadata.ts', '@angular/core/src/di/metadata.ts',
|
||||
'@angular/core/src/di/opaque_token.ts', '@angular/core/src/animation/metadata.ts',
|
||||
'@angular/core/src/di/provider.ts', '@angular/core/src/linker/view.ts'
|
||||
];
|
||||
|
||||
describe('compiler', () => {
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
beforeAll(() => {
|
||||
const emittingHost = new EmittingCompilerHost(CORE_FILES);
|
||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
angularFiles = emittingHost.written;
|
||||
});
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
||||
aotHost = new MockAotCompilerHost(host);
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
it('should compile',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
})));
|
||||
|
||||
it('should compile using summaries',
|
||||
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
function expectNoDiagnostics(program: ts.Program) {
|
||||
function fileInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
||||
|
||||
function lineNoOf(offset: number, text: string): number {
|
||||
let result = 1;
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (text[i] == '\n') result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
const start = diagnostic.start;
|
||||
let end = diagnostic.start + diagnostic.length;
|
||||
const source = diagnostic.file.text;
|
||||
let lineStart = start;
|
||||
let lineEnd = end;
|
||||
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
||||
if (lineStart < start) lineStart++;
|
||||
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
||||
let line = source.substring(lineStart, lineEnd);
|
||||
const lineIndex = line.indexOf('/n');
|
||||
if (lineIndex > 0) {
|
||||
line = line.substr(0, lineIndex);
|
||||
end = start + lineIndex;
|
||||
}
|
||||
const lineNo = lineNoOf(start, source) + ': ';
|
||||
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
||||
chars(end - start, '^');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||
}
|
||||
}
|
||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
function isDTS(fileName: string): boolean {
|
||||
return /\.d\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isSource(fileName: string): boolean {
|
||||
return /\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isFactory(fileName: string): boolean {
|
||||
return /\.ngfactory\./.test(fileName);
|
||||
}
|
||||
|
||||
function summaryCompile(
|
||||
host: MockCompilerHost, aotHost: MockAotCompilerHost,
|
||||
preCompile?: (program: ts.Program) => void) {
|
||||
// First compile the program to generate the summary files.
|
||||
return compile(host, aotHost).then(generatedFiles => {
|
||||
// Remove generated files that were not generated from a DTS file
|
||||
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
|
||||
|
||||
// Next compile the program shrowding metadata and only treating .ts files as source.
|
||||
aotHost.hideMetadata();
|
||||
aotHost.tsFilesOnly();
|
||||
|
||||
return compile(host, aotHost);
|
||||
});
|
||||
}
|
||||
|
||||
function compile(
|
||||
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
||||
postCompile: (program: ts.Program) => void = expectNoDiagnostics) {
|
||||
const program = ts.createProgram(host.scriptNames, settings, host);
|
||||
if (preCompile) preCompile(program);
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, {});
|
||||
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
|
||||
.then(generatedFiles => {
|
||||
generatedFiles.forEach(
|
||||
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
||||
host.override(file.genFileUrl, file.source));
|
||||
const newProgram = ts.createProgram(host.scriptNames, settings, host, program);
|
||||
if (postCompile) postCompile(newProgram);
|
||||
return generatedFiles;
|
||||
});
|
||||
}
|
||||
|
||||
const QUICKSTART = ['/quickstart/app/app.module.ts'];
|
||||
const FILES: MockData = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
|
||||
@Component({
|
||||
template: '<h1>Hello {{name}}</h1>'
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
}
|
||||
`,
|
||||
'app.module.ts': `
|
||||
import { NgModule } from '@angular/core/src/metadata';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
13
modules/@angular/compiler/test/aot/private_import_core.ts
Normal file
13
modules/@angular/compiler/test/aot/private_import_core.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export const reflector: typeof r.reflector = r.reflector;
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
|
||||
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SyntaxError} from '@angular/compiler';
|
||||
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
|
||||
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
|
||||
@ -344,6 +344,20 @@ describe('StaticReflector', () => {
|
||||
'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
|
||||
});
|
||||
|
||||
it('should throw a SyntaxError without stack trace when the required resource cannot be resolved',
|
||||
() => {
|
||||
expect(
|
||||
() => simplify(
|
||||
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'AppModule'), ({
|
||||
__symbolic: 'error',
|
||||
message:
|
||||
'Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts'
|
||||
})))
|
||||
.toThrowError(
|
||||
SyntaxError,
|
||||
'Error encountered resolving symbol values statically. Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts, resolving symbol AppModule in /tmp/src/function-reference.ts');
|
||||
});
|
||||
|
||||
it('should record data about the error in the exception', () => {
|
||||
let threw = false;
|
||||
try {
|
||||
|
389
modules/@angular/compiler/test/aot/test_util.ts
Normal file
389
modules/@angular/compiler/test/aot/test_util.ts
Normal file
@ -0,0 +1,389 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost} from '@angular/compiler';
|
||||
import {MetadataCollector} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export type MockData = string | MockDirectory;
|
||||
|
||||
export type MockDirectory = {
|
||||
[name: string]: MockData | undefined;
|
||||
};
|
||||
|
||||
export function isDirectory(data: MockData): data is MockDirectory {
|
||||
return typeof data !== 'string';
|
||||
}
|
||||
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
|
||||
const rxjs = /\/rxjs\//;
|
||||
const tsxfile = /\.tsx$/;
|
||||
export const settings: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
declaration: true,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
removeComments: false,
|
||||
noImplicitAny: false,
|
||||
skipLibCheck: true,
|
||||
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
|
||||
types: []
|
||||
};
|
||||
|
||||
export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private scriptNames: string[];
|
||||
private root = '/';
|
||||
private collector = new MetadataCollector();
|
||||
|
||||
constructor(scriptNames: string[]) {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'modules');
|
||||
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(
|
||||
f => f.startsWith('@angular/') ? path.join(this.angularSourcePath, f) : f);
|
||||
|
||||
this.root = root;
|
||||
}
|
||||
}
|
||||
|
||||
public getWrittenFiles(): {name: string, content: string}[] {
|
||||
return Array.from(this.writtenFiles).map(f => ({name: f[0], content: f[1]}));
|
||||
}
|
||||
|
||||
public get scripts(): string[] { return this.scriptNames; }
|
||||
|
||||
public get written(): Map<string, string> { return this.writtenFiles; }
|
||||
|
||||
// ts.ModuleResolutionHost
|
||||
fileExists(fileName: string): boolean { return fs.existsSync(fileName); }
|
||||
|
||||
readFile(fileName: string): string {
|
||||
let basename = path.basename(fileName);
|
||||
if (/^lib.*\.d\.ts$/.test(basename)) {
|
||||
let libPath = ts.getDefaultLibFilePath(settings);
|
||||
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
|
||||
}
|
||||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
|
||||
directoryExists(directoryName: string): boolean {
|
||||
return fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory();
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.root; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
return fs.readdirSync(dir).filter(p => {
|
||||
const name = path.join(dir, p);
|
||||
const stat = fs.statSync(name);
|
||||
return stat && stat.isDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
// ts.CompilerHost
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: (message: string) => void): ts.SourceFile {
|
||||
const content = this.readFile(fileName);
|
||||
if (content) {
|
||||
return ts.createSourceFile(fileName, content, languageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
this.writtenFiles.set(fileName, data);
|
||||
if (sourceFiles && sourceFiles.length && DTS.test(fileName)) {
|
||||
const metadataFilePath = fileName.replace(DTS, '.metadata.json');
|
||||
const metadata = this.collector.getMetadata(sourceFiles[0]);
|
||||
if (metadata) this.writtenFiles.set(metadataFilePath, JSON.stringify(metadata));
|
||||
}
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
}
|
||||
|
||||
export class MockCompilerHost implements ts.CompilerHost {
|
||||
scriptNames: string[];
|
||||
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private overrides = new Map<string, string>();
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||
private assumeExists = new Set<string>();
|
||||
private traces: string[] = [];
|
||||
|
||||
constructor(scriptNames: string[], private data: MockData, private angular: Map<string, string>) {
|
||||
this.scriptNames = scriptNames.slice(0);
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
let angularIndex = moduleFilename.indexOf('@angular');
|
||||
let distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'modules');
|
||||
}
|
||||
}
|
||||
|
||||
// Test API
|
||||
override(fileName: string, content: string) {
|
||||
if (content) {
|
||||
this.overrides.set(fileName, content);
|
||||
} else {
|
||||
this.overrides.delete(fileName);
|
||||
}
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
addScript(fileName: string, content: string) {
|
||||
this.overrides.set(fileName, content);
|
||||
this.scriptNames.push(fileName);
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
assumeFileExists(fileName: string) { this.assumeExists.add(fileName); }
|
||||
|
||||
remove(files: string[]) {
|
||||
// Remove the files from the list of scripts.
|
||||
const fileSet = new Set(files);
|
||||
this.scriptNames = this.scriptNames.filter(f => fileSet.has(f));
|
||||
|
||||
// Remove files from written files
|
||||
files.forEach(f => this.writtenFiles.delete(f));
|
||||
}
|
||||
|
||||
// ts.ModuleResolutionHost
|
||||
fileExists(fileName: string): boolean {
|
||||
if (this.overrides.has(fileName) || this.writtenFiles.has(fileName) ||
|
||||
this.assumeExists.has(fileName)) {
|
||||
return true;
|
||||
}
|
||||
const effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName == fileName) {
|
||||
return open(fileName, this.data) != null;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
return fs.existsSync(effectiveName);
|
||||
}
|
||||
return this.angular.has(effectiveName);
|
||||
}
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return this.getFileContent(fileName); }
|
||||
|
||||
trace(s: string): void { this.traces.push(s); }
|
||||
|
||||
getCurrentDirectory(): string { return '/'; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const effectiveName = this.getEffectiveName(dir);
|
||||
if (effectiveName === dir) {
|
||||
const data = find(dir, this.data);
|
||||
if (isDirectory(data)) {
|
||||
return Object.keys(data).filter(k => isDirectory(data[k]));
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// ts.CompilerHost
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: (message: string) => void): ts.SourceFile {
|
||||
let result = this.sourceFiles.get(fileName);
|
||||
if (!result) {
|
||||
const content = this.getFileContent(fileName);
|
||||
if (content) {
|
||||
result = ts.createSourceFile(fileName, content, languageVersion);
|
||||
this.sourceFiles.set(fileName, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean) => {
|
||||
this.writtenFiles.set(fileName, data);
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
|
||||
// Private methods
|
||||
private getFileContent(fileName: string): string|undefined {
|
||||
if (this.overrides.has(fileName)) {
|
||||
return this.overrides.get(fileName);
|
||||
}
|
||||
if (this.writtenFiles.has(fileName)) {
|
||||
return this.writtenFiles.get(fileName);
|
||||
}
|
||||
let basename = path.basename(fileName);
|
||||
if (/^lib.*\.d\.ts$/.test(basename)) {
|
||||
let libPath = ts.getDefaultLibFilePath(settings);
|
||||
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
|
||||
} else {
|
||||
let effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName === fileName)
|
||||
return open(fileName, this.data);
|
||||
else {
|
||||
if (fileName.match(rxjs)) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
}
|
||||
return this.angular.get(effectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveName(name: string): string {
|
||||
const node_modules = 'node_modules';
|
||||
const at_angular = '/@angular';
|
||||
const rxjs = '/rxjs';
|
||||
if (name.startsWith('/' + node_modules)) {
|
||||
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
|
||||
return path.join(this.angularSourcePath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
|
||||
export class MockAotCompilerHost implements AotCompilerHost {
|
||||
private metadataCollector = new MetadataCollector();
|
||||
private metadataVisible: boolean = true;
|
||||
private dtsAreSource: boolean = true;
|
||||
|
||||
constructor(private tsHost: MockCompilerHost) {}
|
||||
|
||||
hideMetadata() { this.metadataVisible = false; }
|
||||
|
||||
tsFilesOnly() { this.dtsAreSource = false; }
|
||||
|
||||
// StaticSymbolResolverHost
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[] {
|
||||
if (!this.tsHost.fileExists(modulePath)) {
|
||||
return undefined;
|
||||
}
|
||||
if (DTS.test(modulePath)) {
|
||||
if (this.metadataVisible) {
|
||||
const metadataPath = modulePath.replace(DTS, '.metadata.json');
|
||||
if (this.tsHost.fileExists(metadataPath)) {
|
||||
let result = JSON.parse(this.tsHost.readFile(metadataPath));
|
||||
return Array.isArray(result) ? result : [result];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const sf = this.tsHost.getSourceFile(modulePath, ts.ScriptTarget.Latest);
|
||||
const metadata = this.metadataCollector.getMetadata(sf);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
|
||||
moduleNameToFileName(moduleName: string, containingFile: string): string|null {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (moduleName.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join('/', 'index.ts');
|
||||
}
|
||||
moduleName = moduleName.replace(EXT, '');
|
||||
const resolved = ts.resolveModuleName(
|
||||
moduleName, containingFile.replace(/\\/g, '/'),
|
||||
{baseDir: '/', genDir: '/'}, this.tsHost)
|
||||
.resolvedModule;
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
}
|
||||
|
||||
// AotSummaryResolverHost
|
||||
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
|
||||
|
||||
isSourceFile(sourceFilePath: string): boolean {
|
||||
return !GENERATED_FILES.test(sourceFilePath) &&
|
||||
(this.dtsAreSource || !DTS.test(sourceFilePath));
|
||||
}
|
||||
|
||||
getOutputFileName(sourceFilePath: string): string {
|
||||
return sourceFilePath.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
|
||||
// AotCompilerHost
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string|null {
|
||||
return importedFile.replace(EXT, '');
|
||||
}
|
||||
|
||||
loadResource(path: string): Promise<string> {
|
||||
return Promise.resolve(this.tsHost.readFile(path));
|
||||
}
|
||||
}
|
||||
|
||||
function find(fileName: string, data: MockData): MockData|undefined {
|
||||
let names = fileName.split('/');
|
||||
if (names.length && !names[0].length) names.shift();
|
||||
let current = data;
|
||||
for (let name of names) {
|
||||
if (typeof current === 'string')
|
||||
return undefined;
|
||||
else
|
||||
current = (<MockDirectory>current)[name];
|
||||
if (!current) return undefined;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function open(fileName: string, data: MockData): string|undefined {
|
||||
let result = find(fileName, data);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function directoryExists(dirname: string, data: MockData): boolean {
|
||||
let result = find(dirname, data);
|
||||
return result && typeof result !== 'string';
|
||||
}
|
@ -342,6 +342,24 @@ export function main() {
|
||||
const HTML = `<div>before<p i18n="m|d">foo</p><!-- comment --></div>`;
|
||||
expect(fakeTranslate(HTML)).toEqual('<div>before<p>**foo**</p></div>');
|
||||
});
|
||||
|
||||
it('should merge empty messages', () => {
|
||||
const HTML = `<div i18n>some element</div>`;
|
||||
const htmlNodes: html.Node[] = parseHtml(HTML);
|
||||
const messages: i18n.Message[] =
|
||||
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
|
||||
|
||||
expect(messages.length).toEqual(1);
|
||||
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
|
||||
i18nMsgMap[digest(messages[0])] = [];
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const output =
|
||||
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
expect(serializeHtmlNodes(output.rootNodes).join('')).toEqual(`<div></div>`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
@ -381,6 +399,25 @@ export function main() {
|
||||
const HTML = `<p i18n-title="m|d" title=""></p>`;
|
||||
expect(fakeTranslate(HTML)).toEqual('<p title=""></p>');
|
||||
});
|
||||
|
||||
it('should merge empty attributes', () => {
|
||||
const HTML = `<div i18n-title title="some attribute">some element</div>`;
|
||||
const htmlNodes: html.Node[] = parseHtml(HTML);
|
||||
const messages: i18n.Message[] =
|
||||
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
|
||||
|
||||
expect(messages.length).toEqual(1);
|
||||
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
|
||||
i18nMsgMap[digest(messages[0])] = [];
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const output =
|
||||
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
expect(serializeHtmlNodes(output.rootNodes).join(''))
|
||||
.toEqual(`<div title="">some element</div>`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -412,12 +449,11 @@ function fakeTranslate(
|
||||
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const translatedNodes =
|
||||
mergeTranslations(
|
||||
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs)
|
||||
.rootNodes;
|
||||
const output = mergeTranslations(
|
||||
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs);
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
return serializeHtmlNodes(translatedNodes).join('');
|
||||
return serializeHtmlNodes(output.rootNodes).join('');
|
||||
}
|
||||
|
||||
function extract(
|
||||
|
@ -141,12 +141,22 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
|
||||
<!-- make sure that ICU messages are not treated as text nodes -->
|
||||
<div i18n="desc">{
|
||||
response.getItemsList().length,
|
||||
plural,
|
||||
=0 {Found no results}
|
||||
=1 {Found one result}
|
||||
other {Found {{response.getItemsList().length}} results}
|
||||
}</div>
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
count: number;
|
||||
sex: string;
|
||||
sexB: string;
|
||||
response: any = {getItemsList: (): any[] => []};
|
||||
}
|
||||
|
||||
class FrLocalization extends NgLocalization {
|
||||
@ -182,6 +192,12 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
|
||||
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex><a></ex></ph>BAR<ph name="CLOSE_LINK"><ex></a></ex></ph></translation>
|
||||
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@ -197,19 +213,24 @@ const XMB = `
|
||||
<msg id="8670732454866344690">on translatable node</msg>
|
||||
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="1746565782635215">
|
||||
<ph name="ICU"/>
|
||||
<ph name="ICU"><ex>ICU</ex></ph>
|
||||
</msg>
|
||||
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
|
||||
<msg id="7685649297917455806">in a translatable section</msg>
|
||||
<msg id="2387287228265107305">
|
||||
<ph name="START_HEADING_LEVEL1"><ex><h1></ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex></h1></ex></ph>
|
||||
<ph name="START_TAG_DIV"><ex><div></ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
|
||||
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex><a></ex></ph>bar<ph name="CLOSE_LINK"><ex></a></ex></ph></msg>
|
||||
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>;
|
||||
</messagebundle>`;
|
||||
|
||||
const HTML = `
|
||||
@ -262,4 +283,6 @@ const HTML = `
|
||||
}</div>
|
||||
|
||||
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
|
||||
|
||||
<div i18n>{{ 'test' //i18n(ph="map name") }}</div>
|
||||
`;
|
||||
|
@ -42,7 +42,7 @@ export function main(): void {
|
||||
});
|
||||
}
|
||||
|
||||
class _TestSerializer implements Serializer {
|
||||
class _TestSerializer extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||
.join('//');
|
||||
|
@ -72,6 +72,11 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
|
||||
<note priority="1" from="description">ph names</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="empty target" datatype="html">
|
||||
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
|
||||
<target/>
|
||||
<note priority="1" from="description">ph names</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@ -88,6 +93,7 @@ export function main(): void {
|
||||
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const i18nNodesByMsgId = serializer.load(xliff, 'url');
|
||||
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
@ -109,6 +115,7 @@ export function main(): void {
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'empty target': '',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -108,9 +108,12 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
|
||||
provide: COMPILER_OPTIONS,
|
||||
useValue: {
|
||||
providers: [
|
||||
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver},
|
||||
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver}
|
||||
MockPipeResolver,
|
||||
{provide: PipeResolver, useExisting: MockPipeResolver},
|
||||
MockDirectiveResolver,
|
||||
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||
MockNgModuleResolver,
|
||||
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
|
||||
]
|
||||
},
|
||||
multi: true
|
||||
|
@ -8,6 +8,16 @@
|
||||
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
/**
|
||||
* `AnimationKeyframe` consists of a series of styles (contained within {@link AnimationStyles
|
||||
* `AnimationStyles`})
|
||||
* and an offset value indicating when those styles are applied within the `duration/delay/easing`
|
||||
* timings.
|
||||
* `AnimationKeyframe` is mostly an internal class which is designed to be used alongside {@link
|
||||
* Renderer#animate-anchor `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationKeyframe {
|
||||
constructor(public offset: number, public styles: AnimationStyles) {}
|
||||
}
|
||||
|
@ -6,6 +6,20 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// having an import prevents dgeni from truncating out
|
||||
// the class description in the docs. DO NOT REMOVE.
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* `AnimationStyles` consists of a collection of key/value maps containing CSS-based style data
|
||||
* that can either be used as initial styling data or apart of a series of keyframes within an
|
||||
* animation.
|
||||
* This class is mostly internal, and it is designed to be used alongside
|
||||
* {@link AnimationKeyframe `AnimationKeyframe`} and {@link Renderer#animate-anchor
|
||||
* `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationStyles {
|
||||
constructor(public styles: {[key: string]: string | number}[]) {}
|
||||
}
|
||||
|
@ -8,13 +8,11 @@
|
||||
|
||||
import {ErrorHandler} from '../src/error_handler';
|
||||
import {ListWrapper} from '../src/facade/collection';
|
||||
import {unimplemented} from '../src/facade/errors';
|
||||
import {stringify} from '../src/facade/lang';
|
||||
import {isPromise} from '../src/util/lang';
|
||||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||
import {Console} from './console';
|
||||
import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di';
|
||||
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
@ -186,9 +184,7 @@ export abstract class PlatformRef {
|
||||
*
|
||||
* @experimental APIs related to application bootstrap are currently under review.
|
||||
*/
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
|
||||
throw unimplemented();
|
||||
}
|
||||
abstract bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>>;
|
||||
|
||||
/**
|
||||
* Creates an instance of an `@NgModule` for a given platform using the given runtime compiler.
|
||||
@ -205,10 +201,9 @@ export abstract class PlatformRef {
|
||||
* ```
|
||||
* @stable
|
||||
*/
|
||||
bootstrapModule<M>(moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = []):
|
||||
Promise<NgModuleRef<M>> {
|
||||
throw unimplemented();
|
||||
}
|
||||
abstract bootstrapModule<M>(
|
||||
moduleType: Type<M>,
|
||||
compilerOptions?: CompilerOptions|CompilerOptions[]): Promise<NgModuleRef<M>>;
|
||||
|
||||
/**
|
||||
* Register a listener to be called when the platform is disposed.
|
||||
@ -219,14 +214,14 @@ export abstract class PlatformRef {
|
||||
* Retrieve the platform {@link Injector}, which is the parent injector for
|
||||
* every Angular application on the page and provides singleton providers.
|
||||
*/
|
||||
get injector(): Injector { throw unimplemented(); };
|
||||
abstract get injector(): Injector;
|
||||
|
||||
/**
|
||||
* Destroy the Angular platform and all Angular applications on the page.
|
||||
*/
|
||||
abstract destroy(): void;
|
||||
|
||||
get destroyed(): boolean { throw unimplemented(); }
|
||||
abstract get destroyed(): boolean;
|
||||
}
|
||||
|
||||
function _callAndReportToErrorHandler(errorHandler: ErrorHandler, callback: () => any): any {
|
||||
@ -331,7 +326,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
||||
}
|
||||
|
||||
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>) {
|
||||
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
|
||||
const appRef = moduleRef.injector.get(ApplicationRef);
|
||||
if (moduleRef.bootstrapFactories.length > 0) {
|
||||
moduleRef.bootstrapFactories.forEach((compFactory) => appRef.bootstrap(compFactory));
|
||||
@ -342,6 +337,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
|
||||
`Please define one of these.`);
|
||||
}
|
||||
this._modules.push(moduleRef);
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,29 +379,29 @@ export abstract class ApplicationRef {
|
||||
* Get a list of component types registered to this application.
|
||||
* This list is populated even before the component is created.
|
||||
*/
|
||||
get componentTypes(): Type<any>[] { return <Type<any>[]>unimplemented(); };
|
||||
abstract get componentTypes(): Type<any>[];
|
||||
|
||||
/**
|
||||
* Get a list of components registered to this application.
|
||||
*/
|
||||
get components(): ComponentRef<any>[] { return <ComponentRef<any>[]>unimplemented(); };
|
||||
abstract get components(): ComponentRef<any>[];
|
||||
|
||||
/**
|
||||
* Attaches a view so that it will be dirty checked.
|
||||
* The view will be automatically detached when it is destroyed.
|
||||
* This will throw if the view is already attached to a ViewContainer.
|
||||
*/
|
||||
attachView(view: ViewRef): void { unimplemented(); }
|
||||
abstract attachView(view: ViewRef): void;
|
||||
|
||||
/**
|
||||
* Detaches a view from dirty checking again.
|
||||
*/
|
||||
detachView(view: ViewRef): void { unimplemented(); }
|
||||
abstract detachView(view: ViewRef): void;
|
||||
|
||||
/**
|
||||
* Returns the number of attached views.
|
||||
*/
|
||||
get viewCount() { return unimplemented(); }
|
||||
abstract get viewCount(): number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -35,4 +35,6 @@ export * from './core_private_export';
|
||||
export * from './animation/metadata';
|
||||
export {AnimationTransitionEvent} from './animation/animation_transition_event';
|
||||
export {AnimationPlayer} from './animation/animation_player';
|
||||
export {AnimationStyles} from './animation/animation_styles';
|
||||
export {AnimationKeyframe} from './animation/animation_keyframe';
|
||||
export {Sanitizer, SecurityContext} from './security';
|
||||
|
@ -41,7 +41,7 @@ import * as reflector_reader from './reflection/reflector_reader';
|
||||
import * as reflection_types from './reflection/types';
|
||||
import * as api from './render/api';
|
||||
import * as decorators from './util/decorators';
|
||||
import {isPromise} from './util/lang';
|
||||
import {isObservable, isPromise} from './util/lang';
|
||||
|
||||
export const __core_private__: {
|
||||
isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy,
|
||||
@ -108,6 +108,7 @@ export const __core_private__: {
|
||||
_ComponentStillLoadingError?: ComponentStillLoadingError,
|
||||
ComponentStillLoadingError: typeof ComponentStillLoadingError,
|
||||
isPromise: typeof isPromise,
|
||||
isObservable: typeof isObservable,
|
||||
AnimationTransition: typeof AnimationTransition
|
||||
view_utils: typeof view_utils,
|
||||
} = {
|
||||
@ -159,5 +160,6 @@ export const __core_private__: {
|
||||
FILL_STYLE_FLAG: FILL_STYLE_FLAG_,
|
||||
ComponentStillLoadingError: ComponentStillLoadingError,
|
||||
isPromise: isPromise,
|
||||
isObservable: isObservable,
|
||||
AnimationTransition: AnimationTransition
|
||||
};
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {stringify} from '../facade/lang';
|
||||
|
||||
const _THROW_IF_NOT_FOUND = new Object();
|
||||
@ -52,5 +51,5 @@ export abstract class Injector {
|
||||
* Injector.THROW_IF_NOT_FOUND is given
|
||||
* - Returns the `notFoundValue` otherwise
|
||||
*/
|
||||
get(token: any, notFoundValue?: any): any { return unimplemented(); }
|
||||
abstract get(token: any, notFoundValue?: any): any;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export interface ValueProvider {
|
||||
useValue: any;
|
||||
|
||||
/**
|
||||
* If true, than injector returns an array of instances. This is useful to allow multiple
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
*
|
||||
* ### Example
|
||||
@ -106,7 +106,7 @@ export interface ClassProvider {
|
||||
useClass: Type<any>;
|
||||
|
||||
/**
|
||||
* If true, than injector returns an array of instances. This is useful to allow multiple
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
*
|
||||
* ### Example
|
||||
@ -144,7 +144,7 @@ export interface ExistingProvider {
|
||||
useExisting: any;
|
||||
|
||||
/**
|
||||
* If true, than injector returns an array of instances. This is useful to allow multiple
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
*
|
||||
* ### Example
|
||||
@ -189,13 +189,13 @@ export interface FactoryProvider {
|
||||
useFactory: Function;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is than
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useFactory` function.
|
||||
*/
|
||||
deps?: any[];
|
||||
|
||||
/**
|
||||
* If true, than injector returns an array of instances. This is useful to allow multiple
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
*
|
||||
* ### Example
|
||||
|
@ -6,9 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {Type} from '../type';
|
||||
|
||||
import {Injector, THROW_IF_NOT_FOUND} from './injector';
|
||||
import {Self, SkipSelf} from './metadata';
|
||||
import {Provider} from './provider';
|
||||
@ -17,308 +14,8 @@ import {ReflectiveKey} from './reflective_key';
|
||||
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
|
||||
|
||||
// Threshold for the dynamic version
|
||||
const _MAX_CONSTRUCTION_COUNTER = 10;
|
||||
const UNDEFINED = new Object();
|
||||
|
||||
export interface ReflectiveProtoInjectorStrategy {
|
||||
getProviderAtIndex(index: number): ResolvedReflectiveProvider;
|
||||
createInjectorStrategy(inj: ReflectiveInjector_): ReflectiveInjectorStrategy;
|
||||
}
|
||||
|
||||
export class ReflectiveProtoInjectorInlineStrategy implements ReflectiveProtoInjectorStrategy {
|
||||
provider0: ResolvedReflectiveProvider = null;
|
||||
provider1: ResolvedReflectiveProvider = null;
|
||||
provider2: ResolvedReflectiveProvider = null;
|
||||
provider3: ResolvedReflectiveProvider = null;
|
||||
provider4: ResolvedReflectiveProvider = null;
|
||||
provider5: ResolvedReflectiveProvider = null;
|
||||
provider6: ResolvedReflectiveProvider = null;
|
||||
provider7: ResolvedReflectiveProvider = null;
|
||||
provider8: ResolvedReflectiveProvider = null;
|
||||
provider9: ResolvedReflectiveProvider = null;
|
||||
|
||||
keyId0: number = null;
|
||||
keyId1: number = null;
|
||||
keyId2: number = null;
|
||||
keyId3: number = null;
|
||||
keyId4: number = null;
|
||||
keyId5: number = null;
|
||||
keyId6: number = null;
|
||||
keyId7: number = null;
|
||||
keyId8: number = null;
|
||||
keyId9: number = null;
|
||||
|
||||
constructor(protoEI: ReflectiveProtoInjector, providers: ResolvedReflectiveProvider[]) {
|
||||
const length = providers.length;
|
||||
|
||||
if (length > 0) {
|
||||
this.provider0 = providers[0];
|
||||
this.keyId0 = providers[0].key.id;
|
||||
}
|
||||
if (length > 1) {
|
||||
this.provider1 = providers[1];
|
||||
this.keyId1 = providers[1].key.id;
|
||||
}
|
||||
if (length > 2) {
|
||||
this.provider2 = providers[2];
|
||||
this.keyId2 = providers[2].key.id;
|
||||
}
|
||||
if (length > 3) {
|
||||
this.provider3 = providers[3];
|
||||
this.keyId3 = providers[3].key.id;
|
||||
}
|
||||
if (length > 4) {
|
||||
this.provider4 = providers[4];
|
||||
this.keyId4 = providers[4].key.id;
|
||||
}
|
||||
if (length > 5) {
|
||||
this.provider5 = providers[5];
|
||||
this.keyId5 = providers[5].key.id;
|
||||
}
|
||||
if (length > 6) {
|
||||
this.provider6 = providers[6];
|
||||
this.keyId6 = providers[6].key.id;
|
||||
}
|
||||
if (length > 7) {
|
||||
this.provider7 = providers[7];
|
||||
this.keyId7 = providers[7].key.id;
|
||||
}
|
||||
if (length > 8) {
|
||||
this.provider8 = providers[8];
|
||||
this.keyId8 = providers[8].key.id;
|
||||
}
|
||||
if (length > 9) {
|
||||
this.provider9 = providers[9];
|
||||
this.keyId9 = providers[9].key.id;
|
||||
}
|
||||
}
|
||||
|
||||
getProviderAtIndex(index: number): ResolvedReflectiveProvider {
|
||||
if (index == 0) return this.provider0;
|
||||
if (index == 1) return this.provider1;
|
||||
if (index == 2) return this.provider2;
|
||||
if (index == 3) return this.provider3;
|
||||
if (index == 4) return this.provider4;
|
||||
if (index == 5) return this.provider5;
|
||||
if (index == 6) return this.provider6;
|
||||
if (index == 7) return this.provider7;
|
||||
if (index == 8) return this.provider8;
|
||||
if (index == 9) return this.provider9;
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
createInjectorStrategy(injector: ReflectiveInjector_): ReflectiveInjectorStrategy {
|
||||
return new ReflectiveInjectorInlineStrategy(injector, this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReflectiveProtoInjectorDynamicStrategy implements ReflectiveProtoInjectorStrategy {
|
||||
keyIds: number[];
|
||||
|
||||
constructor(protoInj: ReflectiveProtoInjector, public providers: ResolvedReflectiveProvider[]) {
|
||||
const len = providers.length;
|
||||
|
||||
this.keyIds = new Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.keyIds[i] = providers[i].key.id;
|
||||
}
|
||||
}
|
||||
|
||||
getProviderAtIndex(index: number): ResolvedReflectiveProvider {
|
||||
if (index < 0 || index >= this.providers.length) {
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
return this.providers[index];
|
||||
}
|
||||
|
||||
createInjectorStrategy(ei: ReflectiveInjector_): ReflectiveInjectorStrategy {
|
||||
return new ReflectiveInjectorDynamicStrategy(this, ei);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReflectiveProtoInjector {
|
||||
static fromResolvedProviders(providers: ResolvedReflectiveProvider[]): ReflectiveProtoInjector {
|
||||
return new ReflectiveProtoInjector(providers);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_strategy: ReflectiveProtoInjectorStrategy;
|
||||
numberOfProviders: number;
|
||||
|
||||
constructor(providers: ResolvedReflectiveProvider[]) {
|
||||
this.numberOfProviders = providers.length;
|
||||
this._strategy = providers.length > _MAX_CONSTRUCTION_COUNTER ?
|
||||
new ReflectiveProtoInjectorDynamicStrategy(this, providers) :
|
||||
new ReflectiveProtoInjectorInlineStrategy(this, providers);
|
||||
}
|
||||
|
||||
getProviderAtIndex(index: number): ResolvedReflectiveProvider {
|
||||
return this._strategy.getProviderAtIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ReflectiveInjectorStrategy {
|
||||
getObjByKeyId(keyId: number): any;
|
||||
getObjAtIndex(index: number): any;
|
||||
getMaxNumberOfObjects(): number;
|
||||
|
||||
resetConstructionCounter(): void;
|
||||
instantiateProvider(provider: ResolvedReflectiveProvider): any;
|
||||
}
|
||||
|
||||
export class ReflectiveInjectorInlineStrategy implements ReflectiveInjectorStrategy {
|
||||
obj0: any = UNDEFINED;
|
||||
obj1: any = UNDEFINED;
|
||||
obj2: any = UNDEFINED;
|
||||
obj3: any = UNDEFINED;
|
||||
obj4: any = UNDEFINED;
|
||||
obj5: any = UNDEFINED;
|
||||
obj6: any = UNDEFINED;
|
||||
obj7: any = UNDEFINED;
|
||||
obj8: any = UNDEFINED;
|
||||
obj9: any = UNDEFINED;
|
||||
|
||||
constructor(
|
||||
public injector: ReflectiveInjector_,
|
||||
public protoStrategy: ReflectiveProtoInjectorInlineStrategy) {}
|
||||
|
||||
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
|
||||
|
||||
instantiateProvider(provider: ResolvedReflectiveProvider): any {
|
||||
return this.injector._new(provider);
|
||||
}
|
||||
|
||||
getObjByKeyId(keyId: number): any {
|
||||
const p = this.protoStrategy;
|
||||
const inj = this.injector;
|
||||
|
||||
if (p.keyId0 === keyId) {
|
||||
if (this.obj0 === UNDEFINED) {
|
||||
this.obj0 = inj._new(p.provider0);
|
||||
}
|
||||
return this.obj0;
|
||||
}
|
||||
if (p.keyId1 === keyId) {
|
||||
if (this.obj1 === UNDEFINED) {
|
||||
this.obj1 = inj._new(p.provider1);
|
||||
}
|
||||
return this.obj1;
|
||||
}
|
||||
if (p.keyId2 === keyId) {
|
||||
if (this.obj2 === UNDEFINED) {
|
||||
this.obj2 = inj._new(p.provider2);
|
||||
}
|
||||
return this.obj2;
|
||||
}
|
||||
if (p.keyId3 === keyId) {
|
||||
if (this.obj3 === UNDEFINED) {
|
||||
this.obj3 = inj._new(p.provider3);
|
||||
}
|
||||
return this.obj3;
|
||||
}
|
||||
if (p.keyId4 === keyId) {
|
||||
if (this.obj4 === UNDEFINED) {
|
||||
this.obj4 = inj._new(p.provider4);
|
||||
}
|
||||
return this.obj4;
|
||||
}
|
||||
if (p.keyId5 === keyId) {
|
||||
if (this.obj5 === UNDEFINED) {
|
||||
this.obj5 = inj._new(p.provider5);
|
||||
}
|
||||
return this.obj5;
|
||||
}
|
||||
if (p.keyId6 === keyId) {
|
||||
if (this.obj6 === UNDEFINED) {
|
||||
this.obj6 = inj._new(p.provider6);
|
||||
}
|
||||
return this.obj6;
|
||||
}
|
||||
if (p.keyId7 === keyId) {
|
||||
if (this.obj7 === UNDEFINED) {
|
||||
this.obj7 = inj._new(p.provider7);
|
||||
}
|
||||
return this.obj7;
|
||||
}
|
||||
if (p.keyId8 === keyId) {
|
||||
if (this.obj8 === UNDEFINED) {
|
||||
this.obj8 = inj._new(p.provider8);
|
||||
}
|
||||
return this.obj8;
|
||||
}
|
||||
if (p.keyId9 === keyId) {
|
||||
if (this.obj9 === UNDEFINED) {
|
||||
this.obj9 = inj._new(p.provider9);
|
||||
}
|
||||
return this.obj9;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
getObjAtIndex(index: number): any {
|
||||
if (index == 0) return this.obj0;
|
||||
if (index == 1) return this.obj1;
|
||||
if (index == 2) return this.obj2;
|
||||
if (index == 3) return this.obj3;
|
||||
if (index == 4) return this.obj4;
|
||||
if (index == 5) return this.obj5;
|
||||
if (index == 6) return this.obj6;
|
||||
if (index == 7) return this.obj7;
|
||||
if (index == 8) return this.obj8;
|
||||
if (index == 9) return this.obj9;
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
getMaxNumberOfObjects(): number { return _MAX_CONSTRUCTION_COUNTER; }
|
||||
}
|
||||
|
||||
|
||||
export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStrategy {
|
||||
objs: any[];
|
||||
|
||||
constructor(
|
||||
public protoStrategy: ReflectiveProtoInjectorDynamicStrategy,
|
||||
public injector: ReflectiveInjector_) {
|
||||
this.objs = new Array(protoStrategy.providers.length).fill(UNDEFINED);
|
||||
}
|
||||
|
||||
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
|
||||
|
||||
instantiateProvider(provider: ResolvedReflectiveProvider): any {
|
||||
return this.injector._new(provider);
|
||||
}
|
||||
|
||||
getObjByKeyId(keyId: number): any {
|
||||
const p = this.protoStrategy;
|
||||
|
||||
for (let i = 0; i < p.keyIds.length; i++) {
|
||||
if (p.keyIds[i] === keyId) {
|
||||
if (this.objs[i] === UNDEFINED) {
|
||||
this.objs[i] = this.injector._new(p.providers[i]);
|
||||
}
|
||||
|
||||
return this.objs[i];
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
getObjAtIndex(index: number): any {
|
||||
if (index < 0 || index >= this.objs.length) {
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
return this.objs[index];
|
||||
}
|
||||
|
||||
getMaxNumberOfObjects(): number { return this.objs.length; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A ReflectiveDependency injection container used for instantiating objects and resolving
|
||||
* dependencies.
|
||||
@ -448,8 +145,7 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
*/
|
||||
static fromResolvedProviders(providers: ResolvedReflectiveProvider[], parent: Injector = null):
|
||||
ReflectiveInjector {
|
||||
return new ReflectiveInjector_(
|
||||
ReflectiveProtoInjector.fromResolvedProviders(providers), parent);
|
||||
return new ReflectiveInjector_(providers, parent);
|
||||
}
|
||||
|
||||
|
||||
@ -467,7 +163,7 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
* expect(child.parent).toBe(parent);
|
||||
* ```
|
||||
*/
|
||||
get parent(): Injector { return unimplemented(); }
|
||||
abstract get parent(): Injector;
|
||||
|
||||
/**
|
||||
* Resolves an array of providers and creates a child injector from those providers.
|
||||
@ -496,7 +192,7 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
* because it needs to resolve the passed-in providers first.
|
||||
* See {@link Injector#resolve} and {@link Injector#createChildFromResolved}.
|
||||
*/
|
||||
resolveAndCreateChild(providers: Provider[]): ReflectiveInjector { return unimplemented(); }
|
||||
abstract resolveAndCreateChild(providers: Provider[]): ReflectiveInjector;
|
||||
|
||||
/**
|
||||
* Creates a child injector from previously resolved providers.
|
||||
@ -523,9 +219,7 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
* expect(child.get(ParentProvider)).toBe(parent.get(ParentProvider));
|
||||
* ```
|
||||
*/
|
||||
createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector {
|
||||
return unimplemented();
|
||||
}
|
||||
abstract createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector;
|
||||
|
||||
/**
|
||||
* Resolves a provider and instantiates an object in the context of the injector.
|
||||
@ -551,7 +245,7 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
* expect(car).not.toBe(injector.resolveAndInstantiate(Car));
|
||||
* ```
|
||||
*/
|
||||
resolveAndInstantiate(provider: Provider): any { return unimplemented(); }
|
||||
abstract resolveAndInstantiate(provider: Provider): any;
|
||||
|
||||
/**
|
||||
* Instantiates an object using a resolved provider in the context of the injector.
|
||||
@ -577,51 +271,52 @@ export abstract class ReflectiveInjector implements Injector {
|
||||
* expect(car).not.toBe(injector.instantiateResolved(carProvider));
|
||||
* ```
|
||||
*/
|
||||
instantiateResolved(provider: ResolvedReflectiveProvider): any { return unimplemented(); }
|
||||
abstract instantiateResolved(provider: ResolvedReflectiveProvider): any;
|
||||
|
||||
abstract get(token: any, notFoundValue?: any): any;
|
||||
}
|
||||
|
||||
export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
private _strategy: ReflectiveInjectorStrategy;
|
||||
/** @internal */
|
||||
_constructionCounter: number = 0;
|
||||
/** @internal */
|
||||
public _proto: any /* ProtoInjector */;
|
||||
public _providers: ResolvedReflectiveProvider[];
|
||||
/** @internal */
|
||||
public _parent: Injector;
|
||||
|
||||
keyIds: number[];
|
||||
objs: any[];
|
||||
/**
|
||||
* Private
|
||||
*/
|
||||
constructor(_proto: any /* ProtoInjector */, _parent: Injector = null) {
|
||||
this._proto = _proto;
|
||||
constructor(_providers: ResolvedReflectiveProvider[], _parent: Injector = null) {
|
||||
this._providers = _providers;
|
||||
this._parent = _parent;
|
||||
this._strategy = _proto._strategy.createInjectorStrategy(this);
|
||||
|
||||
const len = _providers.length;
|
||||
|
||||
this.keyIds = new Array(len);
|
||||
this.objs = new Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.keyIds[i] = _providers[i].key.id;
|
||||
this.objs[i] = UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
|
||||
return this._getByKey(ReflectiveKey.get(token), null, null, notFoundValue);
|
||||
return this._getByKey(ReflectiveKey.get(token), null, notFoundValue);
|
||||
}
|
||||
|
||||
getAt(index: number): any { return this._strategy.getObjAtIndex(index); }
|
||||
|
||||
get parent(): Injector { return this._parent; }
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Internal. Do not use.
|
||||
* We return `any` not to export the InjectorStrategy type.
|
||||
*/
|
||||
get internalStrategy(): any { return this._strategy; }
|
||||
|
||||
resolveAndCreateChild(providers: Provider[]): ReflectiveInjector {
|
||||
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
|
||||
return this.createChildFromResolved(ResolvedReflectiveProviders);
|
||||
}
|
||||
|
||||
createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector {
|
||||
const proto = new ReflectiveProtoInjector(providers);
|
||||
const inj = new ReflectiveInjector_(proto);
|
||||
const inj = new ReflectiveInjector_(providers);
|
||||
inj._parent = this;
|
||||
return inj;
|
||||
}
|
||||
@ -634,14 +329,23 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
return this._instantiateProvider(provider);
|
||||
}
|
||||
|
||||
getProviderAtIndex(index: number): ResolvedReflectiveProvider {
|
||||
if (index < 0 || index >= this._providers.length) {
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
return this._providers[index];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_new(provider: ResolvedReflectiveProvider): any {
|
||||
if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) {
|
||||
if (this._constructionCounter++ > this._getMaxNumberOfObjects()) {
|
||||
throw new CyclicDependencyError(this, provider.key);
|
||||
}
|
||||
return this._instantiateProvider(provider);
|
||||
}
|
||||
|
||||
private _getMaxNumberOfObjects(): number { return this.objs.length; }
|
||||
|
||||
private _instantiateProvider(provider: ResolvedReflectiveProvider): any {
|
||||
if (provider.multiProvider) {
|
||||
const res = new Array(provider.resolvedFactories.length);
|
||||
@ -658,50 +362,11 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
provider: ResolvedReflectiveProvider,
|
||||
ResolvedReflectiveFactory: ResolvedReflectiveFactory): any {
|
||||
const factory = ResolvedReflectiveFactory.factory;
|
||||
const deps = ResolvedReflectiveFactory.dependencies;
|
||||
const length = deps.length;
|
||||
|
||||
let d0: any;
|
||||
let d1: any;
|
||||
let d2: any;
|
||||
let d3: any;
|
||||
let d4: any;
|
||||
let d5: any;
|
||||
let d6: any;
|
||||
let d7: any;
|
||||
let d8: any;
|
||||
let d9: any;
|
||||
let d10: any;
|
||||
let d11: any;
|
||||
let d12: any;
|
||||
let d13: any;
|
||||
let d14: any;
|
||||
let d15: any;
|
||||
let d16: any;
|
||||
let d17: any;
|
||||
let d18: any;
|
||||
let d19: any;
|
||||
let deps: any[];
|
||||
try {
|
||||
d0 = length > 0 ? this._getByReflectiveDependency(provider, deps[0]) : null;
|
||||
d1 = length > 1 ? this._getByReflectiveDependency(provider, deps[1]) : null;
|
||||
d2 = length > 2 ? this._getByReflectiveDependency(provider, deps[2]) : null;
|
||||
d3 = length > 3 ? this._getByReflectiveDependency(provider, deps[3]) : null;
|
||||
d4 = length > 4 ? this._getByReflectiveDependency(provider, deps[4]) : null;
|
||||
d5 = length > 5 ? this._getByReflectiveDependency(provider, deps[5]) : null;
|
||||
d6 = length > 6 ? this._getByReflectiveDependency(provider, deps[6]) : null;
|
||||
d7 = length > 7 ? this._getByReflectiveDependency(provider, deps[7]) : null;
|
||||
d8 = length > 8 ? this._getByReflectiveDependency(provider, deps[8]) : null;
|
||||
d9 = length > 9 ? this._getByReflectiveDependency(provider, deps[9]) : null;
|
||||
d10 = length > 10 ? this._getByReflectiveDependency(provider, deps[10]) : null;
|
||||
d11 = length > 11 ? this._getByReflectiveDependency(provider, deps[11]) : null;
|
||||
d12 = length > 12 ? this._getByReflectiveDependency(provider, deps[12]) : null;
|
||||
d13 = length > 13 ? this._getByReflectiveDependency(provider, deps[13]) : null;
|
||||
d14 = length > 14 ? this._getByReflectiveDependency(provider, deps[14]) : null;
|
||||
d15 = length > 15 ? this._getByReflectiveDependency(provider, deps[15]) : null;
|
||||
d16 = length > 16 ? this._getByReflectiveDependency(provider, deps[16]) : null;
|
||||
d17 = length > 17 ? this._getByReflectiveDependency(provider, deps[17]) : null;
|
||||
d18 = length > 18 ? this._getByReflectiveDependency(provider, deps[18]) : null;
|
||||
d19 = length > 19 ? this._getByReflectiveDependency(provider, deps[19]) : null;
|
||||
deps =
|
||||
ResolvedReflectiveFactory.dependencies.map(dep => this._getByReflectiveDependency(dep));
|
||||
} catch (e) {
|
||||
if (e instanceof AbstractProviderError || e instanceof InstantiationError) {
|
||||
e.addKey(this, provider.key);
|
||||
@ -711,106 +376,45 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
|
||||
let obj: any;
|
||||
try {
|
||||
switch (length) {
|
||||
case 0:
|
||||
obj = factory();
|
||||
break;
|
||||
case 1:
|
||||
obj = factory(d0);
|
||||
break;
|
||||
case 2:
|
||||
obj = factory(d0, d1);
|
||||
break;
|
||||
case 3:
|
||||
obj = factory(d0, d1, d2);
|
||||
break;
|
||||
case 4:
|
||||
obj = factory(d0, d1, d2, d3);
|
||||
break;
|
||||
case 5:
|
||||
obj = factory(d0, d1, d2, d3, d4);
|
||||
break;
|
||||
case 6:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5);
|
||||
break;
|
||||
case 7:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6);
|
||||
break;
|
||||
case 8:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7);
|
||||
break;
|
||||
case 9:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8);
|
||||
break;
|
||||
case 10:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
|
||||
break;
|
||||
case 11:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
|
||||
break;
|
||||
case 12:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
|
||||
break;
|
||||
case 13:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12);
|
||||
break;
|
||||
case 14:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
|
||||
break;
|
||||
case 15:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14);
|
||||
break;
|
||||
case 16:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15);
|
||||
break;
|
||||
case 17:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16);
|
||||
break;
|
||||
case 18:
|
||||
obj = factory(
|
||||
d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17);
|
||||
break;
|
||||
case 19:
|
||||
obj = factory(
|
||||
d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18);
|
||||
break;
|
||||
case 20:
|
||||
obj = factory(
|
||||
d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18,
|
||||
d19);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Cannot instantiate '${provider.key.displayName}' because it has more than 20 dependencies`);
|
||||
}
|
||||
obj = factory(...deps);
|
||||
} catch (e) {
|
||||
throw new InstantiationError(this, e, e.stack, provider.key);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private _getByReflectiveDependency(
|
||||
provider: ResolvedReflectiveProvider, dep: ReflectiveDependency): any {
|
||||
return this._getByKey(
|
||||
dep.key, dep.lowerBoundVisibility, dep.upperBoundVisibility,
|
||||
dep.optional ? null : THROW_IF_NOT_FOUND);
|
||||
private _getByReflectiveDependency(dep: ReflectiveDependency): any {
|
||||
return this._getByKey(dep.key, dep.visibility, dep.optional ? null : THROW_IF_NOT_FOUND);
|
||||
}
|
||||
|
||||
private _getByKey(
|
||||
key: ReflectiveKey, lowerBoundVisibility: Object, upperBoundVisibility: Object,
|
||||
notFoundValue: any): any {
|
||||
private _getByKey(key: ReflectiveKey, visibility: Self|SkipSelf, notFoundValue: any): any {
|
||||
if (key === INJECTOR_KEY) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (upperBoundVisibility instanceof Self) {
|
||||
if (visibility instanceof Self) {
|
||||
return this._getByKeySelf(key, notFoundValue);
|
||||
|
||||
} else {
|
||||
return this._getByKeyDefault(key, notFoundValue, lowerBoundVisibility);
|
||||
return this._getByKeyDefault(key, notFoundValue, visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private _getObjByKeyId(keyId: number): any {
|
||||
for (let i = 0; i < this.keyIds.length; i++) {
|
||||
if (this.keyIds[i] === keyId) {
|
||||
if (this.objs[i] === UNDEFINED) {
|
||||
this.objs[i] = this._new(this._providers[i]);
|
||||
}
|
||||
|
||||
return this.objs[i];
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_throwOrNull(key: ReflectiveKey, notFoundValue: any): any {
|
||||
if (notFoundValue !== THROW_IF_NOT_FOUND) {
|
||||
@ -822,15 +426,15 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
|
||||
/** @internal */
|
||||
_getByKeySelf(key: ReflectiveKey, notFoundValue: any): any {
|
||||
const obj = this._strategy.getObjByKeyId(key.id);
|
||||
const obj = this._getObjByKeyId(key.id);
|
||||
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, notFoundValue);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getByKeyDefault(key: ReflectiveKey, notFoundValue: any, lowerBoundVisibility: Object): any {
|
||||
_getByKeyDefault(key: ReflectiveKey, notFoundValue: any, visibility: Self|SkipSelf): any {
|
||||
let inj: Injector;
|
||||
|
||||
if (lowerBoundVisibility instanceof SkipSelf) {
|
||||
if (visibility instanceof SkipSelf) {
|
||||
inj = this._parent;
|
||||
} else {
|
||||
inj = this;
|
||||
@ -838,7 +442,7 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
|
||||
while (inj instanceof ReflectiveInjector_) {
|
||||
const inj_ = <ReflectiveInjector_>inj;
|
||||
const obj = inj_._strategy.getObjByKeyId(key.id);
|
||||
const obj = inj_._getObjByKeyId(key.id);
|
||||
if (obj !== UNDEFINED) return obj;
|
||||
inj = inj_._parent;
|
||||
}
|
||||
@ -862,9 +466,9 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
|
||||
const INJECTOR_KEY = ReflectiveKey.get(Injector);
|
||||
|
||||
function _mapProviders(injector: ReflectiveInjector_, fn: Function): any[] {
|
||||
const res: any[] = new Array(injector._proto.numberOfProviders);
|
||||
for (let i = 0; i < injector._proto.numberOfProviders; ++i) {
|
||||
res[i] = fn(injector._proto.getProviderAtIndex(i));
|
||||
const res: any[] = new Array(injector._providers.length);
|
||||
for (let i = 0; i < injector._providers.length; ++i) {
|
||||
res[i] = fn(injector.getProviderAtIndex(i));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -25,11 +25,10 @@ interface NormalizedProvider extends TypeProvider, ValueProvider, ClassProvider,
|
||||
*/
|
||||
export class ReflectiveDependency {
|
||||
constructor(
|
||||
public key: ReflectiveKey, public optional: boolean, public lowerBoundVisibility: any,
|
||||
public upperBoundVisibility: any, public properties: any[]) {}
|
||||
public key: ReflectiveKey, public optional: boolean, public visibility: Self|SkipSelf) {}
|
||||
|
||||
static fromKey(key: ReflectiveKey): ReflectiveDependency {
|
||||
return new ReflectiveDependency(key, false, null, null, []);
|
||||
return new ReflectiveDependency(key, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,20 +218,18 @@ function _dependenciesFor(typeOrFunc: any): ReflectiveDependency[] {
|
||||
|
||||
function _extractToken(
|
||||
typeOrFunc: any, metadata: any[] | any, params: any[][]): ReflectiveDependency {
|
||||
const depProps: any[] = [];
|
||||
let token: any = null;
|
||||
let optional = false;
|
||||
|
||||
if (!Array.isArray(metadata)) {
|
||||
if (metadata instanceof Inject) {
|
||||
return _createDependency(metadata.token, optional, null, null, depProps);
|
||||
return _createDependency(metadata.token, optional, null);
|
||||
} else {
|
||||
return _createDependency(metadata, optional, null, null, depProps);
|
||||
return _createDependency(metadata, optional, null);
|
||||
}
|
||||
}
|
||||
|
||||
let lowerBoundVisibility: any = null;
|
||||
let upperBoundVisibility: any = null;
|
||||
let visibility: Self|SkipSelf = null;
|
||||
|
||||
for (let i = 0; i < metadata.length; ++i) {
|
||||
const paramMetadata = metadata[i];
|
||||
@ -246,29 +243,21 @@ function _extractToken(
|
||||
} else if (paramMetadata instanceof Optional) {
|
||||
optional = true;
|
||||
|
||||
} else if (paramMetadata instanceof Self) {
|
||||
upperBoundVisibility = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof Host) {
|
||||
upperBoundVisibility = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof SkipSelf) {
|
||||
lowerBoundVisibility = paramMetadata;
|
||||
} else if (paramMetadata instanceof Self || paramMetadata instanceof SkipSelf) {
|
||||
visibility = paramMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
token = resolveForwardRef(token);
|
||||
|
||||
if (token != null) {
|
||||
return _createDependency(token, optional, lowerBoundVisibility, upperBoundVisibility, depProps);
|
||||
return _createDependency(token, optional, visibility);
|
||||
} else {
|
||||
throw new NoAnnotationError(typeOrFunc, params);
|
||||
}
|
||||
}
|
||||
|
||||
function _createDependency(
|
||||
token: any, optional: boolean, lowerBoundVisibility: any, upperBoundVisibility: any,
|
||||
depProps: any[]): ReflectiveDependency {
|
||||
return new ReflectiveDependency(
|
||||
ReflectiveKey.get(token), optional, lowerBoundVisibility, upperBoundVisibility, depProps);
|
||||
token: any, optional: boolean, visibility: Self | SkipSelf): ReflectiveDependency {
|
||||
return new ReflectiveDependency(ReflectiveKey.get(token), optional, visibility);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {ChangeDetectorRef} from '../change_detection/change_detection';
|
||||
import {Injector} from '../di/injector';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {Type} from '../type';
|
||||
|
||||
import {ElementRef} from './element_ref';
|
||||
@ -30,32 +29,32 @@ export abstract class ComponentRef<C> {
|
||||
/**
|
||||
* Location of the Host Element of this Component Instance.
|
||||
*/
|
||||
get location(): ElementRef { return unimplemented(); }
|
||||
abstract get location(): ElementRef;
|
||||
|
||||
/**
|
||||
* The injector on which the component instance exists.
|
||||
*/
|
||||
get injector(): Injector { return unimplemented(); }
|
||||
abstract get injector(): Injector;
|
||||
|
||||
/**
|
||||
* The instance of the Component.
|
||||
*/
|
||||
get instance(): C { return unimplemented(); };
|
||||
abstract get instance(): C;
|
||||
|
||||
/**
|
||||
* The {@link ViewRef} of the Host View of this Component instance.
|
||||
*/
|
||||
get hostView(): ViewRef { return unimplemented(); };
|
||||
abstract get hostView(): ViewRef;
|
||||
|
||||
/**
|
||||
* The {@link ChangeDetectorRef} of the Component instance.
|
||||
*/
|
||||
get changeDetectorRef(): ChangeDetectorRef { return unimplemented(); }
|
||||
abstract get changeDetectorRef(): ChangeDetectorRef;
|
||||
|
||||
/**
|
||||
* The component type.
|
||||
*/
|
||||
get componentType(): Type<any> { return unimplemented(); }
|
||||
abstract get componentType(): Type<any>;
|
||||
|
||||
/**
|
||||
* Destroys the component instance and all of the data structures associated with it.
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {stringify} from '../facade/lang';
|
||||
import {Type} from '../type';
|
||||
import {ComponentFactory} from './component_factory';
|
||||
@ -27,18 +26,18 @@ export abstract class NgModuleRef<T> {
|
||||
/**
|
||||
* The injector that contains all of the providers of the NgModule.
|
||||
*/
|
||||
get injector(): Injector { return unimplemented(); }
|
||||
abstract get injector(): Injector;
|
||||
|
||||
/**
|
||||
* The ComponentFactoryResolver to get hold of the ComponentFactories
|
||||
* declared in the `entryComponents` property of the module.
|
||||
*/
|
||||
get componentFactoryResolver(): ComponentFactoryResolver { return unimplemented(); }
|
||||
abstract get componentFactoryResolver(): ComponentFactoryResolver;
|
||||
|
||||
/**
|
||||
* The NgModule instance.
|
||||
*/
|
||||
get instance(): T { return unimplemented(); }
|
||||
abstract get instance(): T;
|
||||
|
||||
/**
|
||||
* Destroys the module instance and all of the data structures associated with it.
|
||||
|
@ -37,7 +37,7 @@ export abstract class TemplateRef<C> {
|
||||
*
|
||||
*/
|
||||
// TODO(i): rename to anchor or location
|
||||
get elementRef(): ElementRef { return null; }
|
||||
abstract get elementRef(): ElementRef;
|
||||
|
||||
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
|
||||
|
||||
@ -42,11 +41,11 @@ export abstract class ViewContainerRef {
|
||||
* Anchor element that specifies the location of this container in the containing View.
|
||||
* <!-- TODO: rename to anchorElement -->
|
||||
*/
|
||||
get element(): ElementRef { return <ElementRef>unimplemented(); }
|
||||
abstract get element(): ElementRef;
|
||||
|
||||
get injector(): Injector { return <Injector>unimplemented(); }
|
||||
abstract get injector(): Injector;
|
||||
|
||||
get parentInjector(): Injector { return <Injector>unimplemented(); }
|
||||
abstract get parentInjector(): Injector;
|
||||
|
||||
/**
|
||||
* Destroys all Views in this container.
|
||||
@ -61,7 +60,7 @@ export abstract class ViewContainerRef {
|
||||
/**
|
||||
* Returns the number of Views currently attached to this container.
|
||||
*/
|
||||
get length(): number { return <number>unimplemented(); };
|
||||
abstract get length(): number;
|
||||
|
||||
/**
|
||||
* Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it
|
||||
@ -187,7 +186,8 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
}
|
||||
|
||||
indexOf(viewRef: ViewRef): number {
|
||||
return this._element.nestedViews.indexOf((<ViewRef_<any>>viewRef).internalView);
|
||||
return this.length ? this._element.nestedViews.indexOf((<ViewRef_<any>>viewRef).internalView) :
|
||||
-1;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -214,7 +214,7 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
return wtfLeave(s, view.ref);
|
||||
}
|
||||
|
||||
clear() {
|
||||
clear(): void {
|
||||
for (let i = this.length - 1; i >= 0; i--) {
|
||||
this.remove(i);
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {AnimationQueue} from '../animation/animation_queue';
|
||||
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {ChangeDetectorStatus} from '../change_detection/constants';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {AppView} from './view';
|
||||
|
||||
/**
|
||||
@ -21,7 +20,7 @@ export abstract class ViewRef extends ChangeDetectorRef {
|
||||
*/
|
||||
abstract destroy(): void;
|
||||
|
||||
get destroyed(): boolean { return <boolean>unimplemented(); }
|
||||
abstract get destroyed(): boolean;
|
||||
|
||||
abstract onDestroy(callback: Function): any /** TODO #9100 */;
|
||||
}
|
||||
@ -81,9 +80,9 @@ export abstract class ViewRef extends ChangeDetectorRef {
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class EmbeddedViewRef<C> extends ViewRef {
|
||||
get context(): C { return unimplemented(); }
|
||||
abstract get context(): C;
|
||||
|
||||
get rootNodes(): any[] { return <any[]>unimplemented(); };
|
||||
abstract get rootNodes(): any[];
|
||||
}
|
||||
|
||||
export class ViewRef_<C> implements EmbeddedViewRef<C>, ChangeDetectorRef {
|
||||
|
@ -10,7 +10,6 @@ import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||
import {AnimationPlayer} from '../../src/animation/animation_player';
|
||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
import {Injector} from '../di/injector';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
|
||||
/**
|
||||
@ -25,12 +24,12 @@ export class RenderComponentType {
|
||||
}
|
||||
|
||||
export abstract class RenderDebugInfo {
|
||||
get injector(): Injector { return unimplemented(); }
|
||||
get component(): any { return unimplemented(); }
|
||||
get providerTokens(): any[] { return unimplemented(); }
|
||||
get references(): {[key: string]: any} { return unimplemented(); }
|
||||
get context(): any { return unimplemented(); }
|
||||
get source(): string { return unimplemented(); }
|
||||
abstract get injector(): Injector;
|
||||
abstract get component(): any;
|
||||
abstract get providerTokens(): any[];
|
||||
abstract get references(): {[key: string]: any};
|
||||
abstract get context(): any;
|
||||
abstract get source(): string;
|
||||
}
|
||||
|
||||
export interface DirectRenderer {
|
||||
|
@ -6,8 +6,21 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {$$observable as symbolObservable} from 'rxjs/symbol/observable';
|
||||
|
||||
/**
|
||||
* Determine if the argument is shaped like a Promise
|
||||
*/
|
||||
export function isPromise(obj: any): obj is Promise<any> {
|
||||
// allow any Promise/A+ compliant thenable.
|
||||
// It's up to the caller to ensure that obj.then conforms to the spec
|
||||
return !!obj && typeof obj.then === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the argument is an Observable
|
||||
*/
|
||||
export function isObservable(obj: any | Observable<any>): obj is Observable<any> {
|
||||
return !!(obj && obj[symbolObservable]);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import {EventEmitter} from '../facade/async';
|
||||
* -->
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component, NgZone} from '@angular/core';
|
||||
* import {NgIf} from '@angular/common';
|
||||
@ -76,6 +77,7 @@ import {EventEmitter} from '../facade/async';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class NgZone {
|
||||
|
@ -212,6 +212,11 @@ export function main() {
|
||||
expect(mockConsole.res[0]).toEqual('EXCEPTION: ' + expectedErrMsg);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add bootstrapped module into platform modules list', async(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
|
||||
}));
|
||||
});
|
||||
|
||||
describe('bootstrapModuleFactory', () => {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, Injector, Optional, Provider, ReflectiveInjector, ReflectiveKey, Self, forwardRef} from '@angular/core';
|
||||
import {ReflectiveInjectorDynamicStrategy, ReflectiveInjectorInlineStrategy, ReflectiveInjector_, ReflectiveProtoInjector} from '@angular/core/src/di/reflective_injector';
|
||||
import {ReflectiveInjector_} from '@angular/core/src/di/reflective_injector';
|
||||
import {ResolvedReflectiveProvider_} from '@angular/core/src/di/reflective_provider';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
@ -83,478 +83,440 @@ export function main() {
|
||||
{provide: 'provider10', useValue: 1}
|
||||
];
|
||||
|
||||
[{strategy: 'inline', providers: [], strategyClass: ReflectiveInjectorInlineStrategy}, {
|
||||
strategy: 'dynamic',
|
||||
providers: dynamicProviders,
|
||||
strategyClass: ReflectiveInjectorDynamicStrategy
|
||||
}].forEach((context) => {
|
||||
function createInjector(
|
||||
providers: Provider[], parent: ReflectiveInjector = null): ReflectiveInjector_ {
|
||||
const resolvedProviders = ReflectiveInjector.resolve(providers.concat(context['providers']));
|
||||
if (isPresent(parent)) {
|
||||
return <ReflectiveInjector_>parent.createChildFromResolved(resolvedProviders);
|
||||
} else {
|
||||
return <ReflectiveInjector_>ReflectiveInjector.fromResolvedProviders(resolvedProviders);
|
||||
}
|
||||
function createInjector(
|
||||
providers: Provider[], parent: ReflectiveInjector = null): ReflectiveInjector_ {
|
||||
const resolvedProviders = ReflectiveInjector.resolve(providers.concat(dynamicProviders));
|
||||
if (isPresent(parent)) {
|
||||
return <ReflectiveInjector_>parent.createChildFromResolved(resolvedProviders);
|
||||
} else {
|
||||
return <ReflectiveInjector_>ReflectiveInjector.fromResolvedProviders(resolvedProviders);
|
||||
}
|
||||
}
|
||||
|
||||
describe(`injector ${context['strategy']}`, () => {
|
||||
it('should use the right strategy', () => {
|
||||
const injector = createInjector([]);
|
||||
expect(injector.internalStrategy).toBeAnInstanceOf(context['strategyClass']);
|
||||
});
|
||||
describe(`injector`, () => {
|
||||
|
||||
it('should instantiate a class without dependencies', () => {
|
||||
const injector = createInjector([Engine]);
|
||||
const engine = injector.get(Engine);
|
||||
|
||||
expect(engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should resolve dependencies based on type information', () => {
|
||||
const injector = createInjector([Engine, Car]);
|
||||
const car = injector.get(Car);
|
||||
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should resolve dependencies based on @Inject annotation', () => {
|
||||
const injector = createInjector([TurboEngine, Engine, CarWithInject]);
|
||||
const car = injector.get(CarWithInject);
|
||||
|
||||
expect(car).toBeAnInstanceOf(CarWithInject);
|
||||
expect(car.engine).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should throw when no type and not @Inject (class case)', () => {
|
||||
expect(() => createInjector([NoAnnotations]))
|
||||
.toThrowError(
|
||||
'Cannot resolve all parameters for \'NoAnnotations\'(?). ' +
|
||||
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
|
||||
'and that \'NoAnnotations\' is decorated with Injectable.');
|
||||
});
|
||||
|
||||
it('should throw when no type and not @Inject (factory case)', () => {
|
||||
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
|
||||
.toThrowError(
|
||||
'Cannot resolve all parameters for \'factoryFn\'(?). ' +
|
||||
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
|
||||
'and that \'factoryFn\' is decorated with Injectable.');
|
||||
});
|
||||
|
||||
it('should cache instances', () => {
|
||||
const injector = createInjector([Engine]);
|
||||
|
||||
const e1 = injector.get(Engine);
|
||||
const e2 = injector.get(Engine);
|
||||
|
||||
expect(e1).toBe(e2);
|
||||
});
|
||||
|
||||
it('should provide to a value', () => {
|
||||
const injector = createInjector([{provide: Engine, useValue: 'fake engine'}]);
|
||||
|
||||
const engine = injector.get(Engine);
|
||||
expect(engine).toEqual('fake engine');
|
||||
});
|
||||
|
||||
it('should provide to a factory', () => {
|
||||
function sportsCarFactory(e: any /** TODO #9100 */) { return new SportsCar(e); }
|
||||
|
||||
const injector =
|
||||
createInjector([Engine, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should throw when using a factory with more than 20 dependencies', () => {
|
||||
function factoryWithTooManyArgs() { return new Car(null); }
|
||||
|
||||
const injector = createInjector([
|
||||
Engine, {
|
||||
provide: Car,
|
||||
useFactory: factoryWithTooManyArgs,
|
||||
deps: [
|
||||
Engine, Engine, Engine, Engine, Engine, Engine, Engine,
|
||||
Engine, Engine, Engine, Engine, Engine, Engine, Engine,
|
||||
Engine, Engine, Engine, Engine, Engine, Engine, Engine
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
try {
|
||||
injector.get(Car);
|
||||
throw 'Must throw';
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`Cannot instantiate 'Car' because it has more than 20 dependencies`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should supporting provider to null', () => {
|
||||
const injector = createInjector([{provide: Engine, useValue: null}]);
|
||||
const engine = injector.get(Engine);
|
||||
expect(engine).toBeNull();
|
||||
});
|
||||
|
||||
it('should provide to an alias', () => {
|
||||
const injector = createInjector([
|
||||
Engine, {provide: SportsCar, useClass: SportsCar},
|
||||
{provide: Car, useExisting: SportsCar}
|
||||
]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
const sportsCar = injector.get(SportsCar);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car).toBe(sportsCar);
|
||||
});
|
||||
|
||||
it('should support multiProviders', () => {
|
||||
const injector = createInjector([
|
||||
Engine, {provide: Car, useClass: SportsCar, multi: true},
|
||||
{provide: Car, useClass: CarWithOptionalEngine, multi: true}
|
||||
]);
|
||||
|
||||
const cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(2);
|
||||
expect(cars[0]).toBeAnInstanceOf(SportsCar);
|
||||
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
|
||||
});
|
||||
|
||||
it('should support multiProviders that are created using useExisting', () => {
|
||||
const injector = createInjector(
|
||||
[Engine, SportsCar, {provide: Car, useExisting: SportsCar, multi: true}]);
|
||||
|
||||
const cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(1);
|
||||
expect(cars[0]).toBe(injector.get(SportsCar));
|
||||
});
|
||||
|
||||
it('should throw when the aliased provider does not exist', () => {
|
||||
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
|
||||
const e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
|
||||
expect(() => injector.get('car')).toThrowError(e);
|
||||
});
|
||||
|
||||
it('should handle forwardRef in useExisting', () => {
|
||||
const injector = createInjector([
|
||||
{provide: 'originalEngine', useClass: forwardRef(() => Engine)},
|
||||
{provide: 'aliasedEngine', useExisting: <any>forwardRef(() => 'originalEngine')}
|
||||
]);
|
||||
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should support overriding factory dependencies', () => {
|
||||
const injector = createInjector(
|
||||
[Engine, {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should support optional dependencies', () => {
|
||||
const injector = createInjector([CarWithOptionalEngine]);
|
||||
|
||||
const car = injector.get(CarWithOptionalEngine);
|
||||
expect(car.engine).toEqual(null);
|
||||
});
|
||||
|
||||
it('should flatten passed-in providers', () => {
|
||||
const injector = createInjector([[[Engine, Car]]]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should use the last provider when there are multiple providers for same token', () => {
|
||||
const injector = createInjector(
|
||||
[{provide: Engine, useClass: Engine}, {provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should use non-type tokens', () => {
|
||||
const injector = createInjector([{provide: 'token', useValue: 'value'}]);
|
||||
|
||||
expect(injector.get('token')).toEqual('value');
|
||||
});
|
||||
|
||||
it('should throw when given invalid providers', () => {
|
||||
expect(() => createInjector(<any>['blah']))
|
||||
.toThrowError(
|
||||
'Invalid provider - only instances of Provider and Type are allowed, got: blah');
|
||||
});
|
||||
|
||||
it('should provide itself', () => {
|
||||
const parent = createInjector([]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
|
||||
expect(child.get(Injector)).toBe(child);
|
||||
});
|
||||
|
||||
it('should throw when no provider defined', () => {
|
||||
const injector = createInjector([]);
|
||||
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
|
||||
});
|
||||
|
||||
it('should show the full path when no provider', () => {
|
||||
const injector = createInjector([CarWithDashboard, Engine, Dashboard]);
|
||||
expect(() => injector.get(CarWithDashboard))
|
||||
.toThrowError(
|
||||
`No provider for DashboardSoftware! (${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware)`);
|
||||
});
|
||||
|
||||
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||
const injector = createInjector([Car, {provide: Engine, useClass: CyclicEngine}]);
|
||||
|
||||
expect(() => injector.get(Car))
|
||||
.toThrowError(
|
||||
`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)})`);
|
||||
});
|
||||
|
||||
it('should show the full path when error happens in a constructor', () => {
|
||||
const providers =
|
||||
ReflectiveInjector.resolve([Car, {provide: Engine, useClass: BrokenEngine}]);
|
||||
const proto = new ReflectiveProtoInjector([providers[0], providers[1]]);
|
||||
const injector = new ReflectiveInjector_(proto);
|
||||
|
||||
try {
|
||||
injector.get(Car);
|
||||
throw 'Must throw';
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`Error during instantiation of Engine! (${stringify(Car)} -> Engine)`);
|
||||
expect(e.originalError instanceof Error).toBeTruthy();
|
||||
expect(e.causeKey.token).toEqual(Engine);
|
||||
}
|
||||
});
|
||||
|
||||
it('should instantiate an object after a failed attempt', () => {
|
||||
let isBroken = true;
|
||||
|
||||
const injector = createInjector([
|
||||
Car,
|
||||
{provide: Engine, useFactory: (() => isBroken ? new BrokenEngine() : new Engine())}
|
||||
]);
|
||||
|
||||
expect(() => injector.get(Car))
|
||||
.toThrowError('Broken Engine: Error during instantiation of Engine! (Car -> Engine).');
|
||||
|
||||
isBroken = false;
|
||||
|
||||
expect(injector.get(Car)).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should support null values', () => {
|
||||
const injector = createInjector([{provide: 'null', useValue: null}]);
|
||||
expect(injector.get('null')).toBe(null);
|
||||
});
|
||||
it('should instantiate a class without dependencies', () => {
|
||||
const injector = createInjector([Engine]);
|
||||
const engine = injector.get(Engine);
|
||||
|
||||
expect(engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should resolve dependencies based on type information', () => {
|
||||
const injector = createInjector([Engine, Car]);
|
||||
const car = injector.get(Car);
|
||||
|
||||
describe('child', () => {
|
||||
it('should load instances from parent injector', () => {
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should resolve dependencies based on @Inject annotation', () => {
|
||||
const injector = createInjector([TurboEngine, Engine, CarWithInject]);
|
||||
const car = injector.get(CarWithInject);
|
||||
|
||||
expect(car).toBeAnInstanceOf(CarWithInject);
|
||||
expect(car.engine).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should throw when no type and not @Inject (class case)', () => {
|
||||
expect(() => createInjector([NoAnnotations]))
|
||||
.toThrowError(
|
||||
'Cannot resolve all parameters for \'NoAnnotations\'(?). ' +
|
||||
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
|
||||
'and that \'NoAnnotations\' is decorated with Injectable.');
|
||||
});
|
||||
|
||||
it('should throw when no type and not @Inject (factory case)', () => {
|
||||
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
|
||||
.toThrowError(
|
||||
'Cannot resolve all parameters for \'factoryFn\'(?). ' +
|
||||
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
|
||||
'and that \'factoryFn\' is decorated with Injectable.');
|
||||
});
|
||||
|
||||
it('should cache instances', () => {
|
||||
const injector = createInjector([Engine]);
|
||||
|
||||
const e1 = injector.get(Engine);
|
||||
const e2 = injector.get(Engine);
|
||||
|
||||
expect(e1).toBe(e2);
|
||||
});
|
||||
|
||||
it('should provide to a value', () => {
|
||||
const injector = createInjector([{provide: Engine, useValue: 'fake engine'}]);
|
||||
|
||||
const engine = injector.get(Engine);
|
||||
expect(engine).toEqual('fake engine');
|
||||
});
|
||||
|
||||
it('should provide to a factory', () => {
|
||||
function sportsCarFactory(e: any /** TODO #9100 */) { return new SportsCar(e); }
|
||||
|
||||
const injector =
|
||||
createInjector([Engine, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should supporting provider to null', () => {
|
||||
const injector = createInjector([{provide: Engine, useValue: null}]);
|
||||
const engine = injector.get(Engine);
|
||||
expect(engine).toBeNull();
|
||||
});
|
||||
|
||||
it('should provide to an alias', () => {
|
||||
const injector = createInjector([
|
||||
Engine, {provide: SportsCar, useClass: SportsCar}, {provide: Car, useExisting: SportsCar}
|
||||
]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
const sportsCar = injector.get(SportsCar);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car).toBe(sportsCar);
|
||||
});
|
||||
|
||||
it('should support multiProviders', () => {
|
||||
const injector = createInjector([
|
||||
Engine, {provide: Car, useClass: SportsCar, multi: true},
|
||||
{provide: Car, useClass: CarWithOptionalEngine, multi: true}
|
||||
]);
|
||||
|
||||
const cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(2);
|
||||
expect(cars[0]).toBeAnInstanceOf(SportsCar);
|
||||
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
|
||||
});
|
||||
|
||||
it('should support multiProviders that are created using useExisting', () => {
|
||||
const injector =
|
||||
createInjector([Engine, SportsCar, {provide: Car, useExisting: SportsCar, multi: true}]);
|
||||
|
||||
const cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(1);
|
||||
expect(cars[0]).toBe(injector.get(SportsCar));
|
||||
});
|
||||
|
||||
it('should throw when the aliased provider does not exist', () => {
|
||||
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
|
||||
const e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
|
||||
expect(() => injector.get('car')).toThrowError(e);
|
||||
});
|
||||
|
||||
it('should handle forwardRef in useExisting', () => {
|
||||
const injector = createInjector([
|
||||
{provide: 'originalEngine', useClass: forwardRef(() => Engine)},
|
||||
{provide: 'aliasedEngine', useExisting: <any>forwardRef(() => 'originalEngine')}
|
||||
]);
|
||||
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should support overriding factory dependencies', () => {
|
||||
const injector = createInjector(
|
||||
[Engine, {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(SportsCar);
|
||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should support optional dependencies', () => {
|
||||
const injector = createInjector([CarWithOptionalEngine]);
|
||||
|
||||
const car = injector.get(CarWithOptionalEngine);
|
||||
expect(car.engine).toEqual(null);
|
||||
});
|
||||
|
||||
it('should flatten passed-in providers', () => {
|
||||
const injector = createInjector([[[Engine, Car]]]);
|
||||
|
||||
const car = injector.get(Car);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should use the last provider when there are multiple providers for same token', () => {
|
||||
const injector = createInjector(
|
||||
[{provide: Engine, useClass: Engine}, {provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should use non-type tokens', () => {
|
||||
const injector = createInjector([{provide: 'token', useValue: 'value'}]);
|
||||
|
||||
expect(injector.get('token')).toEqual('value');
|
||||
});
|
||||
|
||||
it('should throw when given invalid providers', () => {
|
||||
expect(() => createInjector(<any>['blah']))
|
||||
.toThrowError(
|
||||
'Invalid provider - only instances of Provider and Type are allowed, got: blah');
|
||||
});
|
||||
|
||||
it('should provide itself', () => {
|
||||
const parent = createInjector([]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
|
||||
expect(child.get(Injector)).toBe(child);
|
||||
});
|
||||
|
||||
it('should throw when no provider defined', () => {
|
||||
const injector = createInjector([]);
|
||||
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
|
||||
});
|
||||
|
||||
it('should show the full path when no provider', () => {
|
||||
const injector = createInjector([CarWithDashboard, Engine, Dashboard]);
|
||||
expect(() => injector.get(CarWithDashboard))
|
||||
.toThrowError(
|
||||
`No provider for DashboardSoftware! (${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware)`);
|
||||
});
|
||||
|
||||
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||
const injector = createInjector([Car, {provide: Engine, useClass: CyclicEngine}]);
|
||||
|
||||
expect(() => injector.get(Car))
|
||||
.toThrowError(
|
||||
`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)})`);
|
||||
});
|
||||
|
||||
it('should show the full path when error happens in a constructor', () => {
|
||||
const providers =
|
||||
ReflectiveInjector.resolve([Car, {provide: Engine, useClass: BrokenEngine}]);
|
||||
const injector = new ReflectiveInjector_(providers);
|
||||
|
||||
try {
|
||||
injector.get(Car);
|
||||
throw 'Must throw';
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`Error during instantiation of Engine! (${stringify(Car)} -> Engine)`);
|
||||
expect(e.originalError instanceof Error).toBeTruthy();
|
||||
expect(e.causeKey.token).toEqual(Engine);
|
||||
}
|
||||
});
|
||||
|
||||
it('should instantiate an object after a failed attempt', () => {
|
||||
let isBroken = true;
|
||||
|
||||
const injector = createInjector([
|
||||
Car, {provide: Engine, useFactory: (() => isBroken ? new BrokenEngine() : new Engine())}
|
||||
]);
|
||||
|
||||
expect(() => injector.get(Car))
|
||||
.toThrowError('Broken Engine: Error during instantiation of Engine! (Car -> Engine).');
|
||||
|
||||
isBroken = false;
|
||||
|
||||
expect(injector.get(Car)).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should support null values', () => {
|
||||
const injector = createInjector([{provide: 'null', useValue: null}]);
|
||||
expect(injector.get('null')).toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('child', () => {
|
||||
it('should load instances from parent injector', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
|
||||
const engineFromParent = parent.get(Engine);
|
||||
const engineFromChild = child.get(Engine);
|
||||
|
||||
expect(engineFromChild).toBe(engineFromParent);
|
||||
});
|
||||
|
||||
it('should not use the child providers when resolving the dependencies of a parent provider',
|
||||
() => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Car, Engine]);
|
||||
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
const carFromChild = child.get(Car);
|
||||
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should create new instance in a child injector', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
const engineFromParent = parent.get(Engine);
|
||||
const engineFromChild = child.get(Engine);
|
||||
|
||||
expect(engineFromParent).not.toBe(engineFromChild);
|
||||
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should give access to parent', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
expect(child.parent).toBe(parent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveAndInstantiate', () => {
|
||||
it('should instantiate an object in the context of the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const car = inj.resolveAndInstantiate(Car);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBe(inj.get(Engine));
|
||||
});
|
||||
|
||||
it('should not store the instantiated object in the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
inj.resolveAndInstantiate(Car);
|
||||
expect(() => inj.get(Car)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('instantiate', () => {
|
||||
it('should instantiate an object in the context of the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const car = inj.instantiateResolved(ReflectiveInjector.resolve([Car])[0]);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBe(inj.get(Engine));
|
||||
});
|
||||
});
|
||||
|
||||
describe('depedency resolution', () => {
|
||||
describe('@Self()', () => {
|
||||
it('should return a dependency from self', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([
|
||||
Engine,
|
||||
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
|
||||
]);
|
||||
|
||||
expect(inj.get(Car)).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should throw when not requested provider on self', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
const child = parent.resolveAndCreateChild(
|
||||
[{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}]);
|
||||
|
||||
const engineFromParent = parent.get(Engine);
|
||||
const engineFromChild = child.get(Engine);
|
||||
|
||||
expect(engineFromChild).toBe(engineFromParent);
|
||||
expect(() => child.get(Car))
|
||||
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use the child providers when resolving the dependencies of a parent provider',
|
||||
() => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Car, Engine]);
|
||||
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
const carFromChild = child.get(Car);
|
||||
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
|
||||
});
|
||||
|
||||
it('should create new instance in a child injector', () => {
|
||||
describe('default', () => {
|
||||
it('should not skip self', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
|
||||
|
||||
const engineFromParent = parent.get(Engine);
|
||||
const engineFromChild = child.get(Engine);
|
||||
|
||||
expect(engineFromParent).not.toBe(engineFromChild);
|
||||
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
it('should give access to parent', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([]);
|
||||
const child = parent.resolveAndCreateChild([]);
|
||||
expect(child.parent).toBe(parent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveAndInstantiate', () => {
|
||||
it('should instantiate an object in the context of the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const car = inj.resolveAndInstantiate(Car);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBe(inj.get(Engine));
|
||||
});
|
||||
|
||||
it('should not store the instantiated object in the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
inj.resolveAndInstantiate(Car);
|
||||
expect(() => inj.get(Car)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('instantiate', () => {
|
||||
it('should instantiate an object in the context of the injector', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const car = inj.instantiateResolved(ReflectiveInjector.resolve([Car])[0]);
|
||||
expect(car).toBeAnInstanceOf(Car);
|
||||
expect(car.engine).toBe(inj.get(Engine));
|
||||
});
|
||||
});
|
||||
|
||||
describe('depedency resolution', () => {
|
||||
describe('@Self()', () => {
|
||||
it('should return a dependency from self', () => {
|
||||
const inj = ReflectiveInjector.resolveAndCreate([
|
||||
Engine,
|
||||
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
|
||||
]);
|
||||
|
||||
expect(inj.get(Car)).toBeAnInstanceOf(Car);
|
||||
});
|
||||
|
||||
it('should throw when not requested provider on self', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([
|
||||
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
|
||||
]);
|
||||
|
||||
expect(() => child.get(Car))
|
||||
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default', () => {
|
||||
it('should not skip self', () => {
|
||||
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
|
||||
const child = parent.resolveAndCreateChild([
|
||||
{provide: Engine, useClass: TurboEngine},
|
||||
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [Engine]}
|
||||
]);
|
||||
|
||||
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
it('should resolve and flatten', () => {
|
||||
const providers = ReflectiveInjector.resolve([Engine, [BrokenEngine]]);
|
||||
providers.forEach(function(b) {
|
||||
if (!b) return; // the result is a sparse array
|
||||
expect(b instanceof ResolvedReflectiveProvider_).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support multi providers', () => {
|
||||
const provider = ReflectiveInjector.resolve([
|
||||
{provide: Engine, useClass: BrokenEngine, multi: true},
|
||||
{provide: Engine, useClass: TurboEngine, multi: true}
|
||||
])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
it('should support providers as hash', () => {
|
||||
const provider = ReflectiveInjector.resolve([
|
||||
{provide: Engine, useClass: BrokenEngine, multi: true},
|
||||
{provide: Engine, useClass: TurboEngine, multi: true}
|
||||
])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should support multi providers with only one provider', () => {
|
||||
const provider =
|
||||
ReflectiveInjector.resolve([{provide: Engine, useClass: BrokenEngine, multi: true}])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should throw when mixing multi providers with regular providers', () => {
|
||||
expect(() => {
|
||||
ReflectiveInjector.resolve(
|
||||
[{provide: Engine, useClass: BrokenEngine, multi: true}, Engine]);
|
||||
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||
|
||||
expect(() => {
|
||||
ReflectiveInjector.resolve(
|
||||
[Engine, {provide: Engine, useClass: BrokenEngine, multi: true}]);
|
||||
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||
});
|
||||
|
||||
it('should resolve forward references', () => {
|
||||
const providers = ReflectiveInjector.resolve([
|
||||
forwardRef(() => Engine),
|
||||
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine)}], {
|
||||
provide: forwardRef(() => String),
|
||||
useFactory: () => 'OK',
|
||||
deps: [forwardRef(() => Engine)]
|
||||
}
|
||||
const child = parent.resolveAndCreateChild([
|
||||
{provide: Engine, useClass: TurboEngine},
|
||||
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [Engine]}
|
||||
]);
|
||||
|
||||
const engineProvider = providers[0];
|
||||
const brokenEngineProvider = providers[1];
|
||||
const stringProvider = providers[2];
|
||||
|
||||
expect(engineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(brokenEngineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(stringProvider.resolvedFactories[0].dependencies[0].key)
|
||||
.toEqual(ReflectiveKey.get(Engine));
|
||||
});
|
||||
|
||||
it('should support overriding factory dependencies with dependency annotations', () => {
|
||||
const providers = ReflectiveInjector.resolve([{
|
||||
provide: 'token',
|
||||
useFactory: (e: any /** TODO #9100 */) => 'result',
|
||||
deps: [[new Inject('dep')]]
|
||||
}]);
|
||||
|
||||
const provider = providers[0];
|
||||
|
||||
expect(provider.resolvedFactories[0].dependencies[0].key.token).toEqual('dep');
|
||||
});
|
||||
|
||||
it('should allow declaring dependencies with flat arrays', () => {
|
||||
const resolved = ReflectiveInjector.resolve(
|
||||
[{provide: 'token', useFactory: (e: any) => e, deps: [new Inject('dep')]}]);
|
||||
const nestedResolved = ReflectiveInjector.resolve(
|
||||
[{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject('dep')]]}]);
|
||||
expect(resolved[0].resolvedFactories[0].dependencies[0].key.token)
|
||||
.toEqual(nestedResolved[0].resolvedFactories[0].dependencies[0].key.token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('displayName', () => {
|
||||
it('should work', () => {
|
||||
expect((<ReflectiveInjector_>ReflectiveInjector.resolveAndCreate([Engine, BrokenEngine]))
|
||||
.displayName)
|
||||
.toEqual('ReflectiveInjector(providers: [ "Engine" , "BrokenEngine" ])');
|
||||
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
it('should resolve and flatten', () => {
|
||||
const providers = ReflectiveInjector.resolve([Engine, [BrokenEngine]]);
|
||||
providers.forEach(function(b) {
|
||||
if (!b) return; // the result is a sparse array
|
||||
expect(b instanceof ResolvedReflectiveProvider_).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support multi providers', () => {
|
||||
const provider = ReflectiveInjector.resolve([
|
||||
{provide: Engine, useClass: BrokenEngine, multi: true},
|
||||
{provide: Engine, useClass: TurboEngine, multi: true}
|
||||
])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
it('should support providers as hash', () => {
|
||||
const provider = ReflectiveInjector.resolve([
|
||||
{provide: Engine, useClass: BrokenEngine, multi: true},
|
||||
{provide: Engine, useClass: TurboEngine, multi: true}
|
||||
])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should support multi providers with only one provider', () => {
|
||||
const provider =
|
||||
ReflectiveInjector.resolve([{provide: Engine, useClass: BrokenEngine, multi: true}])[0];
|
||||
|
||||
expect(provider.key.token).toBe(Engine);
|
||||
expect(provider.multiProvider).toEqual(true);
|
||||
expect(provider.resolvedFactories.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should throw when mixing multi providers with regular providers', () => {
|
||||
expect(() => {
|
||||
ReflectiveInjector.resolve(
|
||||
[{provide: Engine, useClass: BrokenEngine, multi: true}, Engine]);
|
||||
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||
|
||||
expect(() => {
|
||||
ReflectiveInjector.resolve(
|
||||
[Engine, {provide: Engine, useClass: BrokenEngine, multi: true}]);
|
||||
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||
});
|
||||
|
||||
it('should resolve forward references', () => {
|
||||
const providers = ReflectiveInjector.resolve([
|
||||
forwardRef(() => Engine),
|
||||
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine)}], {
|
||||
provide: forwardRef(() => String),
|
||||
useFactory: () => 'OK',
|
||||
deps: [forwardRef(() => Engine)]
|
||||
}
|
||||
]);
|
||||
|
||||
const engineProvider = providers[0];
|
||||
const brokenEngineProvider = providers[1];
|
||||
const stringProvider = providers[2];
|
||||
|
||||
expect(engineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(brokenEngineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(stringProvider.resolvedFactories[0].dependencies[0].key)
|
||||
.toEqual(ReflectiveKey.get(Engine));
|
||||
});
|
||||
|
||||
it('should support overriding factory dependencies with dependency annotations', () => {
|
||||
const providers = ReflectiveInjector.resolve([{
|
||||
provide: 'token',
|
||||
useFactory: (e: any /** TODO #9100 */) => 'result',
|
||||
deps: [[new Inject('dep')]]
|
||||
}]);
|
||||
|
||||
const provider = providers[0];
|
||||
|
||||
expect(provider.resolvedFactories[0].dependencies[0].key.token).toEqual('dep');
|
||||
});
|
||||
|
||||
it('should allow declaring dependencies with flat arrays', () => {
|
||||
const resolved = ReflectiveInjector.resolve(
|
||||
[{provide: 'token', useFactory: (e: any) => e, deps: [new Inject('dep')]}]);
|
||||
const nestedResolved = ReflectiveInjector.resolve(
|
||||
[{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject('dep')]]}]);
|
||||
expect(resolved[0].resolvedFactories[0].dependencies[0].key.token)
|
||||
.toEqual(nestedResolved[0].resolvedFactories[0].dependencies[0].key.token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('displayName', () => {
|
||||
it('should work', () => {
|
||||
expect((<ReflectiveInjector_>ReflectiveInjector.resolveAndCreate([Engine, BrokenEngine]))
|
||||
.displayName)
|
||||
.toEqual('ReflectiveInjector(providers: [ "Engine" , "BrokenEngine" ])');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -5,7 +5,8 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {isPromise} from '@angular/core/src/util/lang';
|
||||
import {isObservable, isPromise} from '@angular/core/src/util/lang';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
export function main() {
|
||||
describe('isPromise', () => {
|
||||
@ -25,4 +26,22 @@ export function main() {
|
||||
expect(isPromise(null)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObservable', () => {
|
||||
it('should be true for an Observable', () => expect(isObservable(of (true))).toEqual(true));
|
||||
|
||||
it('should be false if the argument is undefined',
|
||||
() => expect(isObservable(undefined)).toEqual(false));
|
||||
|
||||
it('should be false if the argument is null', () => expect(isObservable(null)).toEqual(false));
|
||||
|
||||
it('should be false if the argument is an object',
|
||||
() => expect(isObservable({})).toEqual(false));
|
||||
|
||||
it('should be false if the argument is a function',
|
||||
() => expect(isObservable(() => {})).toEqual(false));
|
||||
|
||||
it('should be false if the argument is the object with subscribe function',
|
||||
() => expect(isObservable({subscribe: () => {}})).toEqual(false));
|
||||
});
|
||||
}
|
||||
|
@ -31,14 +31,15 @@ export function async(fn: Function): (done: any) => any {
|
||||
// If we're running using the Jasmine test framework, adapt to call the 'done'
|
||||
// function when asynchronous activity is finished.
|
||||
if (_global.jasmine) {
|
||||
return (done: any) => {
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function(done: any) {
|
||||
if (!done) {
|
||||
// if we run beforeEach in @angular/core/testing/testing_internal then we get no done
|
||||
// fake it here and assume sync.
|
||||
done = function() {};
|
||||
done.fail = function(e: any) { throw e; };
|
||||
}
|
||||
runInTestZone(fn, done, (err: any) => {
|
||||
runInTestZone(fn, this, done, (err: any) => {
|
||||
if (typeof err === 'string') {
|
||||
return done.fail(new Error(<string>err));
|
||||
} else {
|
||||
@ -50,12 +51,16 @@ export function async(fn: Function): (done: any) => any {
|
||||
// Otherwise, return a promise which will resolve when asynchronous activity
|
||||
// is finished. This will be correctly consumed by the Mocha framework with
|
||||
// it('...', async(myFn)); or can be used in a custom framework.
|
||||
return () => new Promise<void>((finishCallback, failCallback) => {
|
||||
runInTestZone(fn, finishCallback, failCallback);
|
||||
});
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function() {
|
||||
return new Promise<void>((finishCallback, failCallback) => {
|
||||
runInTestZone(fn, this, finishCallback, failCallback);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function runInTestZone(fn: Function, finishCallback: Function, failCallback: Function) {
|
||||
function runInTestZone(
|
||||
fn: Function, context: any, finishCallback: Function, failCallback: Function) {
|
||||
const currentZone = Zone.current;
|
||||
const AsyncTestZoneSpec = (Zone as any)['AsyncTestZoneSpec'];
|
||||
if (AsyncTestZoneSpec === undefined) {
|
||||
@ -103,5 +108,5 @@ function runInTestZone(fn: Function, finishCallback: Function, failCallback: Fun
|
||||
'test');
|
||||
proxyZoneSpec.setDelegate(testZoneSpec);
|
||||
});
|
||||
return Zone.current.runGuarded(fn);
|
||||
return Zone.current.runGuarded(fn, context);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ let _inFakeAsyncCall = false;
|
||||
* @experimental
|
||||
*/
|
||||
export function fakeAsync(fn: Function): (...args: any[]) => any {
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function(...args: any[]) {
|
||||
const proxyZoneSpec = ProxyZoneSpec.assertPresent();
|
||||
if (_inFakeAsyncCall) {
|
||||
@ -67,7 +68,7 @@ export function fakeAsync(fn: Function): (...args: any[]) => any {
|
||||
const lastProxyZoneSpec = proxyZoneSpec.getDelegate();
|
||||
proxyZoneSpec.setDelegate(_fakeAsyncTestZoneSpec);
|
||||
try {
|
||||
res = fn(...args);
|
||||
res = fn.apply(this, args);
|
||||
flushMicrotasks();
|
||||
} finally {
|
||||
proxyZoneSpec.setDelegate(lastProxyZoneSpec);
|
||||
|
@ -319,10 +319,10 @@ export class TestBed implements Injector {
|
||||
return result === UNDEFINED ? this._compiler.injector.get(token, notFoundValue) : result;
|
||||
}
|
||||
|
||||
execute(tokens: any[], fn: Function): any {
|
||||
execute(tokens: any[], fn: Function, context?: any): any {
|
||||
this._initIfNeeded();
|
||||
const params = tokens.map(t => this.get(t));
|
||||
return fn(...params);
|
||||
return fn.apply(context, params);
|
||||
}
|
||||
|
||||
overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void {
|
||||
@ -407,17 +407,19 @@ export function getTestBed() {
|
||||
export function inject(tokens: any[], fn: Function): () => any {
|
||||
const testBed = getTestBed();
|
||||
if (tokens.indexOf(AsyncTestCompleter) >= 0) {
|
||||
return () =>
|
||||
// Return an async test method that returns a Promise if AsyncTestCompleter is one of
|
||||
// the
|
||||
// injected tokens.
|
||||
testBed.compileComponents().then(() => {
|
||||
const completer: AsyncTestCompleter = testBed.get(AsyncTestCompleter);
|
||||
testBed.execute(tokens, fn);
|
||||
return completer.promise;
|
||||
});
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function() {
|
||||
// Return an async test method that returns a Promise if AsyncTestCompleter is one of
|
||||
// the injected tokens.
|
||||
return testBed.compileComponents().then(() => {
|
||||
const completer: AsyncTestCompleter = testBed.get(AsyncTestCompleter);
|
||||
testBed.execute(tokens, fn, this);
|
||||
return completer.promise;
|
||||
});
|
||||
};
|
||||
} else {
|
||||
return () => testBed.execute(tokens, fn);
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function() { return testBed.execute(tokens, fn, this); };
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,9 +437,11 @@ export class InjectSetupWrapper {
|
||||
}
|
||||
|
||||
inject(tokens: any[], fn: Function): () => any {
|
||||
return () => {
|
||||
this._addModule();
|
||||
return inject(tokens, fn)();
|
||||
const self = this;
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function() {
|
||||
self._addModule();
|
||||
return inject(tokens, fn).call(this);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -450,12 +454,13 @@ export function withModule(moduleDef: TestModuleMetadata, fn: Function): () => a
|
||||
export function withModule(moduleDef: TestModuleMetadata, fn: Function = null): (() => any)|
|
||||
InjectSetupWrapper {
|
||||
if (fn) {
|
||||
return () => {
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function() {
|
||||
const testBed = getTestBed();
|
||||
if (moduleDef) {
|
||||
testBed.configureTestingModule(moduleDef);
|
||||
}
|
||||
return fn();
|
||||
return fn.apply(this);
|
||||
};
|
||||
}
|
||||
return new InjectSetupWrapper(() => moduleDef);
|
||||
|
@ -1,92 +0,0 @@
|
||||
# TL;DR;
|
||||
|
||||
* If you write ES5 use _one_ of the `UMD` bundles.
|
||||
* If you experiment with Angular2 using online prototyping tools like [plnkr](http://plnkr.co/) or similar use `System.register` bundles with SystemJS loader.
|
||||
* If you use build tools like Browserify or WebPack - bundle Angular2 as part of your build.
|
||||
* For all the above cases you must use `angular2-polyfills.js` in a `script` tag to easily include polyfills and external dependencies.
|
||||
|
||||
# Modules, barrels and bundles
|
||||
|
||||
Angular2 source code is authored using the ES2015 standardized module format where one module corresponds to exactly one file. Multiple modules (files) can be logically grouped into so-called "barrels".
|
||||
A bundle is a file that contains all the code for one or more barrels.
|
||||
|
||||
Most bundles come in several flavors:
|
||||
* regular and minified (got `.min` in their name);
|
||||
* regular and "development" (have `.dev` in their name) - "development" bundles contain in-line source maps and don't have minified flavor (minification removes in-lined source maps).
|
||||
|
||||
# Bundles, their content and usage scenarios
|
||||
|
||||
Angular 2 distributes several types of bundles targeted at specific usages:
|
||||
* users writing ES5 code without any transpilation steps
|
||||
* users experimenting with Angular 2 and TypeScript/ES2015 using online tools like plunker, jsbin or similar
|
||||
|
||||
Since each identified scenario has slightly different requirements and constraints there are specific bundles for each use-case.
|
||||
|
||||
## ES5 and ngUpgrade users
|
||||
|
||||
ES5 users and AngularJS 1.x users interested in the `ngUpgrade` path can take advantage of the bundles in the [UMD format](https://github.com/umdjs/umd).
|
||||
Those are coarse-grained bundles that combine many barrels in one final file.
|
||||
|
||||
filename | list of barrels | dev/prod | minified?
|
||||
------------|-------------------|----------|-------------|--------------|-------------
|
||||
`angular2-all.umd.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/http`, `angular2/router`, `angular2/instrumentation`, `angular2/upgrade`| prod | no
|
||||
`angular2-all.umd.min.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/http`, `angular2/router`, `angular2/instrumentation`, `angular2/upgrade` | prod | yes
|
||||
`angular2-all.umd.dev.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/http`, `angular2/router`, `angular2/instrumentation`, `angular2/upgrade` | dev | no
|
||||
`angular2-all-testing.umd.dev.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/http`, `angular2/router`, `angular2/instrumentation`, `angular2/upgrade`, `angular2/testing`, `angular2/http/testing`, `angular2/router/testing`, `angular2/platform/testing/browser` | dev | no
|
||||
|
||||
**Warning**: bundles in the `UMD` format are _not_ "additive". A single application should use only one bundle from the above list.
|
||||
|
||||
## SystemJS loader users
|
||||
|
||||
[SystemJS loader](https://github.com/systemjs/systemjs) with on-the-fly (in a browser) transpilations support is very useful for quick experiments using tools like plunker, jsbin or similar.
|
||||
For this scenario Angular 2 is distributed with bundles in the [System.register format](https://github.com/ModuleLoader/es6-module-loader/wiki/System.register-Explained):
|
||||
|
||||
filename | list of barrels | dev/prod | minified?
|
||||
------------|-------------------|----------|-------------|--------------|-------------
|
||||
`angular2.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/instrumentation`| prod | no
|
||||
`angular2.min.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/instrumentation`| prod | yes
|
||||
`angular2.dev.js` | `angular2/core`, `angular2/common`, `angular2/compiler`, `angular2/platform/browser`, `angular2/platform/common_dom`, `angular2/instrumentation`| dev | no
|
||||
`http.js` | `angular2/http` | prod | no
|
||||
`http.min.js` | `angular2/http` | prod | yes
|
||||
`http.dev.js` | `angular2/http` | dev | no
|
||||
`router.js` | `angular2/router` | prod | no
|
||||
`router.min.js` | `angular2/router` | prod | yes
|
||||
`router.dev.js` | `angular2/router` | dev | no
|
||||
`upgrade.js` | `angular2/upgrade` | prod | no
|
||||
`upgrade.min.js` | `angular2/upgrade` | prod | yes
|
||||
`upgrade.dev.js` | `angular2/upgrade` | dev | no
|
||||
`testing.dev.js` | `angular2/testing`, `angular2/http/testing`, `angular2/router/testing`, `angular2/platform/testing/browser` | dev | no
|
||||
|
||||
**Note**: bundles in the `System.register` format are "additive" - it is quite common to include several bundles in one application.
|
||||
For example people using Angular 2 with `http` and `router` would include: `angular2.js`, `http.js` and `router.js`.
|
||||
|
||||
## Browserify / JSPM / Rollup / WebPack users
|
||||
|
||||
Angular 2 doesn't provide any bundles for use with packaging tools Browserify or WebPack. Those tools are sophisticated enough to build optimal bundles for production use from individual Angular 2 files distributed in the npm package.
|
||||
An example of an Angular 2 project built with WebPack can be found in the [angular2-seed](https://github.com/angular/angular2-seed) repository.
|
||||
|
||||
|
||||
# Polyfills and external dependencies
|
||||
|
||||
## Required Polyfills
|
||||
|
||||
Polyfills are required for Angular 2 to function properly (the exact list depends on the browser used) and external dependencies ([zone.js](https://github.com/angular/zone.js)).
|
||||
To ease setup of Angular 2 applications there is one file - `angular2-polyfills.js` - that combines:
|
||||
* a polyfill mandatory for all browsers: [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
|
||||
* [zone.js](https://github.com/angular/zone.js)
|
||||
|
||||
**Note**: `angular2-polyfills.js` contains code that should be loaded into the browser as the very first code of the web application even before the module loader. The preferred solution is to load the mentioned file in a `script` tag as early as possible.
|
||||
|
||||
|
||||
## RxJS
|
||||
|
||||
[RxJS](https://github.com/ReactiveX/RxJS) is a required dependency of Angular 2.
|
||||
|
||||
You should include RxJS in your project by declaring a dependency on the [`rxjs` npm package](https://www.npmjs.com/package/rxjs).
|
||||
|
||||
Depending on if you are using Angular bundles or not you can either use RxJS bundles from `node_modules/rxjs/bundles/` or configure your bundler to pull in the individual files from the npm package.
|
||||
|
||||
|
||||
## ES6 shims (optional)
|
||||
|
||||
Users of pre-ES6 browsers might need to add an ES6 shim (e.g. [core-js](https://github.com/zloirock/core-js))
|
@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience to throw an Error with 'unimplemented' as the message.
|
||||
*/
|
||||
export function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
@ -81,10 +81,6 @@ export function isStrictStringMap(obj: any): boolean {
|
||||
return typeof obj === 'object' && obj !== null && Object.getPrototypeOf(obj) === STRING_MAP_PROTO;
|
||||
}
|
||||
|
||||
export function isDate(obj: any): obj is Date {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
export function stringify(token: any): string {
|
||||
if (typeof token === 'string') {
|
||||
return token;
|
||||
|
@ -86,7 +86,11 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
const valueString = _buildValueString(this._getOptionId(value), value);
|
||||
const id: string = this._getOptionId(value);
|
||||
if (id == null) {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'selectedIndex', -1);
|
||||
}
|
||||
const valueString = _buildValueString(id, value);
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,8 @@ import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
import {composeAsyncValidators, composeValidators} from './directives/shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {EventEmitter, Observable} from './facade/async';
|
||||
import {isPromise} from './private_import_core';
|
||||
import {isObservable, isPromise} from './private_import_core';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -419,6 +420,10 @@ export abstract class AbstractControl {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
const obs = toObservable(this.asyncValidator(this));
|
||||
if (!(isObservable(obs))) {
|
||||
throw new Error(
|
||||
`expected the following validator to return Promise or Observable: ${this.asyncValidator}. If you are using FormBuilder; did you forget to brace your validators in an array?`);
|
||||
}
|
||||
this._asyncValidationSubscription =
|
||||
obs.subscribe({next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent})});
|
||||
}
|
||||
@ -1015,7 +1020,7 @@ export class FormGroup extends AbstractControl {
|
||||
getRawValue(): any {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
acc[name] = control instanceof FormControl ? control.value : (<any>control).getRawValue();
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
@ -1098,8 +1103,8 @@ export class FormGroup extends AbstractControl {
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Tracks the value and validity state of an array of {@link FormControl}
|
||||
* instances.
|
||||
* @whatItDoes Tracks the value and validity state of an array of {@link FormControl},
|
||||
* {@link FormGroup} or {@link FormArray} instances.
|
||||
*
|
||||
* A `FormArray` aggregates the values of each child {@link FormControl} into an array.
|
||||
* It calculates its status by reducing the statuses of its children. For example, if one of
|
||||
@ -1316,7 +1321,11 @@ export class FormArray extends AbstractControl {
|
||||
* If you'd like to include all values regardless of disabled status, use this method.
|
||||
* Otherwise, the `value` property is the best way to get the value of the array.
|
||||
*/
|
||||
getRawValue(): any[] { return this.controls.map((control) => control.value); }
|
||||
getRawValue(): any[] {
|
||||
return this.controls.map((control: AbstractControl) => {
|
||||
return control instanceof FormControl ? control.value : (<any>control).getRawValue();
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_throwIfControlMissing(index: number): void {
|
||||
|
@ -9,3 +9,4 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export const isPromise: typeof r.isPromise = r.isPromise;
|
||||
export const isObservable: typeof r.isObservable = r.isObservable;
|
||||
|
@ -32,6 +32,7 @@ export function main() {
|
||||
}
|
||||
|
||||
describe('FormArray', () => {
|
||||
|
||||
describe('adding/removing', () => {
|
||||
let a: FormArray;
|
||||
let c1: FormControl, c2: FormControl, c3: FormControl;
|
||||
@ -81,6 +82,21 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRawValue()', () => {
|
||||
let a: FormArray;
|
||||
|
||||
it('should work with nested form groups/arrays', () => {
|
||||
a = new FormArray([
|
||||
new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
|
||||
new FormArray([new FormControl('v4'), new FormControl('v5')])
|
||||
]);
|
||||
a.at(0).get('c3').disable();
|
||||
(a.at(1) as FormArray).at(1).disable();
|
||||
|
||||
expect(a.getRawValue()).toEqual([{'c2': 'v2', 'c3': 'v3'}, ['v4', 'v5']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setValue', () => {
|
||||
let c: FormControl, c2: FormControl, a: FormArray;
|
||||
|
||||
|
@ -40,6 +40,8 @@ export function main() {
|
||||
|
||||
function otherAsyncValidator() { return Promise.resolve({'other': true}); }
|
||||
|
||||
function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ { return null; }
|
||||
|
||||
describe('FormControl', () => {
|
||||
it('should default the value to null', () => {
|
||||
const c = new FormControl();
|
||||
@ -977,6 +979,15 @@ export function main() {
|
||||
expect(logger).toEqual(['control', 'group']);
|
||||
});
|
||||
|
||||
it('should throw when sync validator passed into async validator param', () => {
|
||||
const fn = () => new FormControl('', syncValidator, syncValidator);
|
||||
// test for the specific error since without the error check it would still throw an error
|
||||
// but
|
||||
// not a meaningful one
|
||||
expect(fn).toThrowError(
|
||||
`expected the following validator to return Promise or Observable: ${syncValidator}. If you are using FormBuilder; did you forget to brace your validators in an array?`);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {AbstractControl, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
|
||||
import {EventEmitter} from '../src/facade/async';
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
@ -62,6 +62,24 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRawValue', () => {
|
||||
let fg: FormGroup;
|
||||
|
||||
it('should work with nested form groups/arrays', () => {
|
||||
fg = new FormGroup({
|
||||
'c1': new FormControl('v1'),
|
||||
'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
|
||||
'array': new FormArray([new FormControl('v4'), new FormControl('v5')])
|
||||
});
|
||||
fg.get('group').get('c3').disable();
|
||||
(fg.get('array') as FormArray).at(1).disable();
|
||||
|
||||
expect(fg.getRawValue())
|
||||
.toEqual({'c1': 'v1', 'group': {'c2': 'v2', 'c3': 'v3'}, 'array': ['v4', 'v5']});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('adding and removing controls', () => {
|
||||
it('should update value and validity when control is added', () => {
|
||||
const g = new FormGroup({'one': new FormControl('1')});
|
||||
|
@ -43,6 +43,10 @@ export function main() {
|
||||
it('should error on null',
|
||||
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
|
||||
|
||||
it('should not error on undefined', () => {
|
||||
expect(Validators.required(new FormControl(undefined))).toEqual({'required': true});
|
||||
});
|
||||
|
||||
it('should not error on a non-empty string',
|
||||
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
|
||||
|
||||
@ -72,7 +76,7 @@ export function main() {
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
||||
() => { expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull(); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||
@ -103,6 +107,9 @@ export function main() {
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull(); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||
|
||||
@ -132,8 +139,9 @@ export function main() {
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
||||
it('should not error on undefined', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
|
||||
});
|
||||
|
||||
it('should not error on null value and "null" pattern',
|
||||
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
|
||||
|
@ -181,7 +181,7 @@ export class XHRConnection implements Connection {
|
||||
|
||||
/**
|
||||
* `XSRFConfiguration` sets up Cross Site Request Forgery (XSRF) protection for the application
|
||||
* using a cookie. See {@link https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)}
|
||||
* using a cookie. See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||
* for more information on XSRF.
|
||||
*
|
||||
* Applications can configure custom cookie and header names by binding an instance of this class
|
||||
|
@ -66,9 +66,11 @@ export interface RequestArgs extends RequestOptionsArgs { url: string; }
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export type ResponseOptionsArgs = {
|
||||
body?: string | Object | FormData | ArrayBuffer | Blob; status?: number; statusText?: string;
|
||||
export interface ResponseOptionsArgs {
|
||||
body?: string|Object|FormData|ArrayBuffer|Blob;
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers?: Headers;
|
||||
type?: ResponseType;
|
||||
url?: string;
|
||||
};
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ export class Request extends Body {
|
||||
case 'text/html':
|
||||
return ContentType.TEXT;
|
||||
case 'application/octet-stream':
|
||||
return ContentType.BLOB;
|
||||
return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
|
||||
default:
|
||||
return this.detectContentTypeFromBody();
|
||||
}
|
||||
@ -132,7 +132,7 @@ export class Request extends Body {
|
||||
return ContentType.BLOB;
|
||||
} else if (this._body instanceof ArrayBuffer) {
|
||||
return ContentType.ARRAY_BUFFER;
|
||||
} else if (this._body && typeof this._body == 'object') {
|
||||
} else if (this._body && typeof this._body === 'object') {
|
||||
return ContentType.JSON;
|
||||
} else {
|
||||
return ContentType.TEXT;
|
||||
@ -167,4 +167,4 @@ const noop = function() {};
|
||||
const w = typeof window == 'object' ? window : noop;
|
||||
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
|
||||
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
|
||||
const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
|
||||
export const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
|
||||
|
@ -36,7 +36,7 @@ import {Headers} from './headers';
|
||||
*/
|
||||
export class Response extends Body {
|
||||
/**
|
||||
* One of "basic", "cors", "default", "error, or "opaque".
|
||||
* One of "basic", "cors", "default", "error", or "opaque".
|
||||
*
|
||||
* Defaults to "default".
|
||||
*/
|
||||
|
@ -11,7 +11,7 @@ import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {RequestOptions} from '../src/base_request_options';
|
||||
import {ContentType} from '../src/enums';
|
||||
import {Headers} from '../src/headers';
|
||||
import {Request} from '../src/static_request';
|
||||
import {ArrayBuffer, Request} from '../src/static_request';
|
||||
|
||||
export function main() {
|
||||
describe('Request', () => {
|
||||
@ -76,6 +76,17 @@ export function main() {
|
||||
|
||||
expect(req.detectContentType()).toEqual(ContentType.BLOB);
|
||||
});
|
||||
|
||||
it('should not create a blob out of ArrayBuffer', () => {
|
||||
const req = new Request(new RequestOptions({
|
||||
url: 'test',
|
||||
method: 'GET',
|
||||
body: new ArrayBuffer(1),
|
||||
headers: new Headers({'content-type': 'application/octet-stream'})
|
||||
}));
|
||||
|
||||
expect(req.detectContentType()).toEqual(ContentType.ARRAY_BUFFER);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty string if no body is present', () => {
|
||||
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Completions, LanguageService} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
|
||||
describe('service without angular', () => {
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
mockHost.forgetAngular();
|
||||
let service = ts.createLanguageService(mockHost);
|
||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||
let ngService = createLanguageService(ngHost);
|
||||
const fileName = '/app/test.ng';
|
||||
let position = mockHost.getMarkerLocations(fileName)['h1-content'];
|
||||
|
||||
it('should not crash a get template references',
|
||||
() => expect(() => ngService.getTemplateReferences()));
|
||||
it('should not crash a get dianostics',
|
||||
() => expect(() => ngService.getDiagnostics(fileName)).not.toThrow());
|
||||
it('should not crash a completion',
|
||||
() => expect(() => ngService.getCompletionsAt(fileName, position)).not.toThrow());
|
||||
it('should not crash a get defintion',
|
||||
() => expect(() => ngService.getDefinitionAt(fileName, position)).not.toThrow());
|
||||
it('should not crash a hover', () => expect(() => ngService.getHoverAt(fileName, position)));
|
||||
});
|
@ -97,6 +97,8 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
|
||||
this.scriptNames.push(fileName);
|
||||
}
|
||||
|
||||
forgetAngular() { this.angularPath = undefined; }
|
||||
|
||||
getCompilationSettings(): ts.CompilerOptions {
|
||||
return {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @license Angular v0.0.0-PLACEHOLDER
|
||||
* (c) 2010-2016 Google, Inc. https://angular.io/
|
||||
* (c) 2010-2017 Google, Inc. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
@ -127,7 +127,6 @@ export class DomRenderer implements Renderer {
|
||||
let nodesParent: Element|DocumentFragment;
|
||||
if (this.componentProto.encapsulation === ViewEncapsulation.Native) {
|
||||
nodesParent = (hostElement as any).createShadowRoot();
|
||||
this._rootRenderer.sharedStylesHost.addHost(nodesParent);
|
||||
for (let i = 0; i < this._styles.length; i++) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = this._styles[i];
|
||||
|
@ -6,58 +6,58 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {Inject, Injectable, OnDestroy} from '@angular/core';
|
||||
import {getDOM} from './dom_adapter';
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
|
||||
@Injectable()
|
||||
export class SharedStylesHost {
|
||||
/** @internal */
|
||||
_styles: string[] = [];
|
||||
/** @internal */
|
||||
_stylesSet = new Set<string>();
|
||||
protected _stylesSet = new Set<string>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
addStyles(styles: string[]) {
|
||||
const additions: any[] /** TODO #9100 */ = [];
|
||||
addStyles(styles: string[]): void {
|
||||
const additions = new Set<string>();
|
||||
styles.forEach(style => {
|
||||
if (!this._stylesSet.has(style)) {
|
||||
this._stylesSet.add(style);
|
||||
this._styles.push(style);
|
||||
additions.push(style);
|
||||
additions.add(style);
|
||||
}
|
||||
});
|
||||
this.onStylesAdded(additions);
|
||||
}
|
||||
|
||||
onStylesAdded(additions: string[]) {}
|
||||
onStylesAdded(additions: Set<string>): void {}
|
||||
|
||||
getAllStyles(): string[] { return this._styles; }
|
||||
getAllStyles(): string[] { return Array.from(this._stylesSet); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomSharedStylesHost extends SharedStylesHost {
|
||||
export class DomSharedStylesHost extends SharedStylesHost implements OnDestroy {
|
||||
private _hostNodes = new Set<Node>();
|
||||
constructor(@Inject(DOCUMENT) doc: any) {
|
||||
private _styleNodes = new Set<Node>();
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||
super();
|
||||
this._hostNodes.add(doc.head);
|
||||
this._hostNodes.add(_doc.head);
|
||||
}
|
||||
/** @internal */
|
||||
_addStylesToHost(styles: string[], host: Node) {
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = styles[i];
|
||||
host.appendChild(styleEl);
|
||||
}
|
||||
|
||||
private _addStylesToHost(styles: Set<string>, host: Node): void {
|
||||
styles.forEach((style: string) => {
|
||||
const styleEl = this._doc.createElement('style');
|
||||
styleEl.textContent = style;
|
||||
this._styleNodes.add(host.appendChild(styleEl));
|
||||
});
|
||||
}
|
||||
addHost(hostNode: Node) {
|
||||
this._addStylesToHost(this._styles, hostNode);
|
||||
|
||||
addHost(hostNode: Node): void {
|
||||
this._addStylesToHost(this._stylesSet, hostNode);
|
||||
this._hostNodes.add(hostNode);
|
||||
}
|
||||
removeHost(hostNode: Node) { this._hostNodes.delete(hostNode); }
|
||||
|
||||
onStylesAdded(additions: string[]) {
|
||||
this._hostNodes.forEach((hostNode) => { this._addStylesToHost(additions, hostNode); });
|
||||
removeHost(hostNode: Node): void { this._hostNodes.delete(hostNode); }
|
||||
|
||||
onStylesAdded(additions: Set<string>): void {
|
||||
this._hostNodes.forEach(hostNode => this._addStylesToHost(additions, hostNode));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._styleNodes.forEach(styleNode => getDOM().remove(styleNode)); }
|
||||
}
|
||||
|
@ -96,7 +96,9 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
|
||||
/** @internal */
|
||||
_triggerWebAnimation(element: any, keyframes: any[], options: any): DomAnimatePlayer {
|
||||
return <DomAnimatePlayer>element.animate(keyframes, options);
|
||||
// jscompiler doesn't seem to know animate is a native property because it's not fully
|
||||
// supported yet across common browsers (we polyfill it for Edge/Safari) [CL #143630929]
|
||||
return <DomAnimatePlayer>element['animate'](keyframes, options);
|
||||
}
|
||||
|
||||
get domPlayer() { return this._player; }
|
||||
|
@ -30,9 +30,14 @@ import {sanitizeUrl} from './url_sanitizer';
|
||||
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
|
||||
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?';
|
||||
const COLOR_FNS = '(?:rgb|hsl)a?';
|
||||
const FN_ARGS = '\\([-0-9.%, a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE =
|
||||
new RegExp(`^(${VALUES}|(?:${TRANSFORMATION_FNS}|${COLOR_FNS})${FN_ARGS})$`, 'g');
|
||||
const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
|
||||
const CSS3_FNS = '(?:calc|attr)';
|
||||
const FN_ARGS = '\\([-0-9.%, #a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE = new RegExp(
|
||||
`^(${VALUES}|` +
|
||||
`(?:${TRANSFORMATION_FNS}|${COLOR_FNS}|${GRADIENTS}|${CSS3_FNS})` +
|
||||
`${FN_ARGS})$`,
|
||||
'g');
|
||||
|
||||
/**
|
||||
* Matches a `url(...)` value with an arbitrary argument as long as it does
|
||||
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {describe, it} from '@angular/core/testing/testing_internal';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('DomRenderer', () => {
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({imports: [BrowserModule, TestModule]}));
|
||||
|
||||
// other browsers don't support shadow dom
|
||||
if (browserDetection.isChromeDesktop) {
|
||||
it('should add only styles with native encapsulation to the shadow DOM', () => {
|
||||
const fixture = TestBed.createComponent(SomeApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const cmp = fixture.debugElement.query(By.css('cmp-native')).nativeElement;
|
||||
const styles = cmp.shadowRoot.querySelectorAll('style');
|
||||
expect(styles.length).toBe(1);
|
||||
expect(styles[0]).toHaveText('.cmp-native { color: red; }');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-native',
|
||||
template: ``,
|
||||
styles: [`.cmp-native { color: red; }`],
|
||||
encapsulation: ViewEncapsulation.Native
|
||||
})
|
||||
class CmpEncapsulationNative {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-emulated',
|
||||
template: ``,
|
||||
styles: [`.cmp-emulated { color: blue; }`],
|
||||
encapsulation: ViewEncapsulation.Emulated
|
||||
})
|
||||
class CmpEncapsulationEmulated {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-none',
|
||||
template: ``,
|
||||
styles: [`.cmp-none { color: yellow; }`],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
class CmpEncapsulationNone {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-app',
|
||||
template: `
|
||||
<cmp-native></cmp-native>
|
||||
<cmp-emulated></cmp-emulated>
|
||||
<cmp-none></cmp-none>
|
||||
`,
|
||||
})
|
||||
export class SomeApp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SomeApp,
|
||||
CmpEncapsulationNative,
|
||||
CmpEncapsulationEmulated,
|
||||
CmpEncapsulationNone,
|
||||
],
|
||||
imports: [CommonModule]
|
||||
})
|
||||
class TestModule {
|
||||
}
|
@ -13,7 +13,7 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('DomSharedStylesHost', () => {
|
||||
let doc: any /** TODO #9100 */;
|
||||
let doc: Document;
|
||||
let ssh: DomSharedStylesHost;
|
||||
let someHost: Element;
|
||||
beforeEach(() => {
|
||||
@ -46,5 +46,14 @@ export function main() {
|
||||
ssh.addStyles(['a {};', 'b {};']);
|
||||
expect(doc.head).toHaveText('a {};b {};');
|
||||
});
|
||||
|
||||
it('should remove style nodes on destroy', () => {
|
||||
ssh.addStyles(['a {};']);
|
||||
ssh.addHost(someHost);
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('<style>a {};</style>');
|
||||
|
||||
ssh.ngOnDestroy();
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -39,6 +39,16 @@ export function main() {
|
||||
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
|
||||
expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)');
|
||||
});
|
||||
t.it('accepts gradients', () => {
|
||||
expectSanitize('linear-gradient(to bottom, #fg34a1, #bada55)')
|
||||
.toEqual('linear-gradient(to bottom, #fg34a1, #bada55)');
|
||||
expectSanitize('repeating-radial-gradient(ellipse cover, black, red, black, red)')
|
||||
.toEqual('repeating-radial-gradient(ellipse cover, black, red, black, red)');
|
||||
});
|
||||
t.it('accepts calc', () => { expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)'); });
|
||||
t.it('accepts attr', () => {
|
||||
expectSanitize('attr(value string)').toEqual('attr(value string)');
|
||||
});
|
||||
t.it('sanitizes URLs', () => {
|
||||
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
|
||||
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');
|
||||
|
@ -114,31 +114,63 @@ class CompWithUrlTemplate {
|
||||
|
||||
export function main() {
|
||||
describe('public testing API', () => {
|
||||
describe('using the async helper', () => {
|
||||
let actuallyDone: boolean;
|
||||
describe('using the async helper with context passing', () => {
|
||||
beforeEach(function() { this.actuallyDone = false; });
|
||||
|
||||
beforeEach(() => actuallyDone = false);
|
||||
afterEach(function() { expect(this.actuallyDone).toEqual(true); });
|
||||
|
||||
afterEach(() => expect(actuallyDone).toEqual(true));
|
||||
it('should run normal tests', function() { this.actuallyDone = true; });
|
||||
|
||||
it('should run normal tests', () => actuallyDone = true);
|
||||
|
||||
it('should run normal async tests', (done) => {
|
||||
it('should run normal async tests', function(done) {
|
||||
setTimeout(() => {
|
||||
actuallyDone = true;
|
||||
this.actuallyDone = true;
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should run async tests with tasks',
|
||||
async(() => setTimeout(() => actuallyDone = true, 0)));
|
||||
async(function() { setTimeout(() => this.actuallyDone = true, 0); }));
|
||||
|
||||
it('should run async tests with promises', async(() => {
|
||||
it('should run async tests with promises', async(function() {
|
||||
const p = new Promise((resolve, reject) => setTimeout(resolve, 10));
|
||||
p.then(() => actuallyDone = true);
|
||||
p.then(() => this.actuallyDone = true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('basic context passing to inject, fakeAsync and withModule helpers', () => {
|
||||
const moduleConfig = {
|
||||
providers: [FancyService],
|
||||
};
|
||||
|
||||
beforeEach(function() { this.contextModified = false; });
|
||||
|
||||
afterEach(function() { expect(this.contextModified).toEqual(true); });
|
||||
|
||||
it('should pass context to inject helper',
|
||||
inject([], function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to fakeAsync helper',
|
||||
fakeAsync(function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to withModule helper - simple',
|
||||
withModule(moduleConfig, function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to withModule helper - advanced',
|
||||
withModule(moduleConfig).inject([FancyService], function(service: FancyService) {
|
||||
expect(service.value).toBe('real value');
|
||||
this.contextModified = true;
|
||||
}));
|
||||
|
||||
it('should preserve context when async and inject helpers are combined',
|
||||
async(inject([], function() { setTimeout(() => this.contextModified = true, 0); })));
|
||||
|
||||
it('should preserve context when fakeAsync and inject helpers are combined',
|
||||
fakeAsync(inject([], function() {
|
||||
setTimeout(() => this.contextModified = true, 0);
|
||||
tick(1);
|
||||
})));
|
||||
});
|
||||
|
||||
describe('using the test injector with the inject helper', () => {
|
||||
describe('setting up Providers', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @license Angular v0.0.0-ROUTERPLACEHOLDER
|
||||
* (c) 2010-2016 Google, Inc. https://angular.io/
|
||||
* (c) 2010-2017 Google, Inc. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Google, Inc. http://angular.io
|
||||
Copyright (c) 2017 Google, Inc. http://angular.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user