Compare commits
116 Commits
2.4.2
...
4.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
f114e40212 | |||
952471e25d | |||
c65e428778 | |||
842f52e841 | |||
eb2ceff4ba | |||
f49ab56160 | |||
c0f750af4e | |||
bcd37f52fb | |||
e69c1fb36c | |||
9da4c259a5 | |||
fcd116fdc0 | |||
383adc9ad9 | |||
9b8488f007 | |||
1817ddb57b | |||
1ee574c51e | |||
171a9bdc85 | |||
896916af29 | |||
e49c7fae22 | |||
6b65fc1286 | |||
0e3981afc1 | |||
e78508507d | |||
a23fa94ca8 | |||
4568d5ddac | |||
c6e893953f | |||
55dfa1b69d | |||
0fe3cd9a4c | |||
0c19898694 | |||
5b6e8ea3ec | |||
732f446ad2 | |||
f0e092515c | |||
14e785f5b7 | |||
01d1624884 | |||
33910ddfc9 | |||
01ca2db6ae | |||
6cefccb314 | |||
fa9e21e83c | |||
b6078f5887 | |||
c65b4fa9dc | |||
169ed82900 | |||
fd8e15b15d | |||
aa40366a92 | |||
40d8d9c3e3 | |||
ee2ac025ef | |||
aa3769ba69 | |||
d4ddb6004e | |||
84400bcc86 | |||
42d9998cbb | |||
c18d2fe5e3 | |||
d91a86aac6 | |||
d6e5e9283c | |||
eab7e490c9 | |||
3e90605db9 | |||
79671a6f12 | |||
a659259962 | |||
b56474d067 | |||
8395f0e138 | |||
dd0519abad | |||
f238c8ac7a | |||
8c27c62fab | |||
5031adc7a3 | |||
821b8f09d6 | |||
2bf1bbc071 | |||
7b0a86718c | |||
3edca4d37e | |||
a0a05041ac | |||
7256d0ede5 | |||
d62d89319e | |||
f5f1d5f65c | |||
a8d237581d | |||
d036165a19 | |||
d17e690eb4 | |||
714f2af0dd | |||
2b90cd532f | |||
3a64ad895a | |||
9ec0a4e105 | |||
4b3d135193 | |||
1d0ed6f75f | |||
6f330a5fc9 | |||
e23076f767 | |||
7295a5e7f2 | |||
20bed46737 | |||
2a5012d515 | |||
fb38fba8f9 | |||
4c35be3e07 | |||
e9f307f948 | |||
2e500cc85b | |||
56dce0e26d | |||
8a8c53250e | |||
08ff2e5249 | |||
a006c1418a | |||
90c223591f | |||
aaf6e05f56 | |||
3bee521aa4 | |||
95f48292b1 | |||
04cfa1ebdf | |||
4022173d1e | |||
c8baf51f4f | |||
b4db73d0bf | |||
e15a3f273f | |||
213c713409 | |||
9a8423da36 | |||
f0b0762f4a | |||
b5c4bf1c59 | |||
56c361ff6a | |||
562f7a2f8b | |||
6dd5201765 | |||
72361fb68f | |||
5c6ec20c7e | |||
440ef02f29 | |||
4e3d58a792 | |||
61d7c1e0b3 | |||
bf93389615 | |||
4398056146 | |||
1b547886d0 | |||
9591a08dfb | |||
65965c27a8 |
46
CHANGELOG.md
46
CHANGELOG.md
@ -1,27 +1,11 @@
|
||||
<a name="2.4.2"></a>
|
||||
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
|
||||
<a name="4.0.0-beta.1"></a>
|
||||
# [4.0.0-beta.1](https://github.com/angular/angular/compare/2.4.0-marker...4.0.0-beta.1) (2016-12-22)
|
||||
|
||||
### Features
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
|
||||
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
|
||||
* **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)
|
||||
* **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))
|
||||
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
|
||||
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
|
||||
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
|
||||
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
|
||||
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
|
||||
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
|
||||
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
|
||||
|
||||
* **upgrade:** support the `$doCheck()` lifecycle hook in `UpgradeComponent` ([#13015](https://github.com/angular/angular/issues/13015)) ([9da4c25](https://github.com/angular/angular/commit/9da4c25))
|
||||
|
||||
Note: 4.0.0-beta.1 release also contains all the changes present in the 2.4.0 and the 2.4.1 releases.
|
||||
|
||||
<a name="2.4.1"></a>
|
||||
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
||||
@ -55,6 +39,25 @@
|
||||
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
|
||||
|
||||
|
||||
<a name="4.0.0-beta.0"></a>
|
||||
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
|
||||
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
|
||||
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
|
||||
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
|
||||
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
|
||||
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
|
||||
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
|
||||
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
|
||||
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
|
||||
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
|
||||
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
|
||||
|
||||
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
|
||||
|
||||
<a name="2.3.1"></a>
|
||||
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
|
||||
@ -109,7 +112,6 @@ The >=2.3.1 compiler will issue is the following error if it encounters componen
|
||||
|
||||
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
|
||||
|
||||
|
||||
<a name="2.3.0"></a>
|
||||
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
|
||||
|
||||
|
11
DEVELOPER.md
11
DEVELOPER.md
@ -133,8 +133,7 @@ If you happen to modify the public API of Angular, API golden files must be upda
|
||||
$ gulp public-api:update
|
||||
```
|
||||
|
||||
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
|
||||
the project before trying to verify after an API change.
|
||||
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||
|
||||
## <a name="clang-format"></a> Formatting your source code
|
||||
|
||||
@ -147,14 +146,6 @@ You can automatically format your code by running:
|
||||
$ gulp format
|
||||
```
|
||||
|
||||
## Linting/verifying your source code
|
||||
|
||||
You can check that your code is properly formatted and adheres to coding style by running:
|
||||
|
||||
``` shell
|
||||
$ gulp lint
|
||||
```
|
||||
|
||||
## Publishing your own personal snapshot build
|
||||
|
||||
You may find that your un-merged change needs some validation from external participants.
|
||||
|
@ -12,9 +12,9 @@
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
export * from './location/index';
|
||||
export {NgLocalization} from './localization';
|
||||
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||
export {CommonModule} from './common_module';
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||
export {VERSION} from './version';
|
||||
export {Version} from '@angular/core';
|
||||
|
@ -25,8 +25,6 @@ import {isPresent, stringify} from '../facade/lang';
|
||||
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
||||
*
|
||||
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
||||
*
|
||||
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {getTypeNameForDebugging} from '../facade/lang';
|
||||
|
||||
@ -91,13 +91,8 @@ export class NgFor implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: any;
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFn) {
|
||||
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
||||
// TODO(vicb): use a log service once there is a public one available
|
||||
if (<any>console && <any>console.warn) {
|
||||
console.warn(
|
||||
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
|
||||
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
|
||||
}
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
|
||||
}
|
||||
this._trackByFn = fn;
|
||||
}
|
||||
|
@ -6,46 +6,152 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
* Conditionally includes a template based on the value of an `expression`.
|
||||
*
|
||||
* If the expression assigned to `ngIf` evaluates to a falsy value then the element
|
||||
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
|
||||
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
|
||||
* when expression is thruthy or falsy respectively. Typically the:
|
||||
* - `then` template is the inline template of `ngIf` unless bound to a different value.
|
||||
* - `else` template is blank unless its bound.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
|
||||
* # Most common usage
|
||||
*
|
||||
* The most common usage of the `ngIf` is to conditionally show the inline template as seen in this
|
||||
* example:
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
|
||||
*
|
||||
* # Showing an alternative template using `else`
|
||||
*
|
||||
* If it is necessary to display a template when the `expression` is falsy use the `else` template
|
||||
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
|
||||
* The template can be defined anywhere in the component view but is typically placed right after
|
||||
* `ngIf` for readability.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
|
||||
*
|
||||
* # Using non-inlined `then` template
|
||||
*
|
||||
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
|
||||
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
|
||||
* change at runtime as shown in thise example.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
|
||||
*
|
||||
* # Storing conditional result in a variable
|
||||
*
|
||||
* A common patter is that we need to show a set of properties from the same object. if the
|
||||
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
|
||||
* dereferencing a `null` value. This is especially the case when waiting on async data such as
|
||||
* when using the `async` pipe as shown in folowing example:
|
||||
*
|
||||
* ```
|
||||
* <div *ngIf="errorCount > 0" class="error">
|
||||
* <!-- Error message displayed when the errorCount property in the current context is greater
|
||||
* than 0. -->
|
||||
* {{errorCount}} errors detected
|
||||
* </div>
|
||||
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
|
||||
* ```
|
||||
*
|
||||
* There are several inefficiencies in the above example.
|
||||
* - We create multiple subscriptions on the `userStream`. One for each `async` pipe, or two
|
||||
* as shown in the example above.
|
||||
* - We can not display an alternative screen while waiting for the data to arrive asynchronously.
|
||||
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
|
||||
* - We have to place the `async` pipe in parenthesis.
|
||||
*
|
||||
* A better way to do this is to use `ngIf` and store the result of the condition in a local
|
||||
* variable as shown in the the example below:
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
|
||||
*
|
||||
* Notice that:
|
||||
* - We use only one `async` pipe and hence only one subscription gets created.
|
||||
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
|
||||
* - The local `user` can than be bound repeatedly in a more efficient way.
|
||||
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
|
||||
* display the data if `userStream` returns a value.
|
||||
* - We can display an alternative template while waiting for the data.
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* Simple form:
|
||||
* - `<div *ngIf="condition">...</div>`
|
||||
* - `<div template="ngIf condition">...</div>`
|
||||
* - `<template [ngIf]="condition"><div>...</div></template>`
|
||||
*
|
||||
* Form with an else block:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock">...</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with a `then` and `else` block:
|
||||
* ```
|
||||
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
|
||||
* <template #thenBlock>...</template>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with storing the value locally:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngIf]'})
|
||||
export class NgIf {
|
||||
private _hasView = false;
|
||||
private _context: NgIfContext = new NgIfContext();
|
||||
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
|
||||
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIf(condition: any) {
|
||||
if (condition && !this._hasView) {
|
||||
this._hasView = true;
|
||||
this._viewContainer.createEmbeddedView(this._template);
|
||||
} else if (!condition && this._hasView) {
|
||||
this._hasView = false;
|
||||
this._viewContainer.clear();
|
||||
this._context.$implicit = condition;
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
this._thenViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._elseTemplateRef = templateRef;
|
||||
this._elseViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
private _updateView() {
|
||||
if (this._context.$implicit) {
|
||||
if (!this._thenViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._elseViewRef = null;
|
||||
if (this._thenTemplateRef) {
|
||||
this._thenViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this._elseViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._thenViewRef = null;
|
||||
if (this._elseTemplateRef) {
|
||||
this._elseViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NgIfContext { public $implicit: any = null; }
|
||||
|
@ -49,10 +49,10 @@ export function getPluralCategory(
|
||||
*/
|
||||
@Injectable()
|
||||
export class NgLocaleLocalization extends NgLocalization {
|
||||
constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
|
||||
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
|
||||
|
||||
getPluralCategory(value: any): string {
|
||||
const plural = getPluralCase(this._locale, value);
|
||||
const plural = getPluralCase(this.locale, value);
|
||||
|
||||
switch (plural) {
|
||||
case Plural.Zero:
|
||||
@ -431,4 +431,4 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
|
||||
default:
|
||||
return Plural.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* Transforms text to lowercase.
|
||||
*
|
||||
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe' }
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to transform a single word to titlecase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
function titleCaseWord(word: string) {
|
||||
if (!word) return word;
|
||||
return word[0].toUpperCase() + word.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to titlecase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'titlecase'})
|
||||
export class TitleCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(TitleCasePipe, value);
|
||||
}
|
||||
|
||||
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to uppercase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -12,14 +12,13 @@
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
import {AsyncPipe} from './async_pipe';
|
||||
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
|
||||
export {
|
||||
AsyncPipe,
|
||||
@ -32,9 +31,11 @@ export {
|
||||
LowerCasePipe,
|
||||
PercentPipe,
|
||||
SlicePipe,
|
||||
TitleCasePipe,
|
||||
UpperCasePipe
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A collection of Angular pipes that are likely to be used in each and every application.
|
||||
*/
|
||||
@ -46,6 +47,7 @@ export const COMMON_PIPES = [
|
||||
SlicePipe,
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
TitleCasePipe,
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
I18nPluralPipe,
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to lowercase.
|
||||
* @howToUse `expression | lowercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into a lowercase string using `String.prototype.toLowerCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to uppercase.
|
||||
* @howToUse `expression | uppercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into an uppercase string using `String.prototype.toUpperCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -294,25 +294,14 @@ export function main() {
|
||||
}));
|
||||
|
||||
describe('track by', () => {
|
||||
it('should console.warn if trackBy is not a function', async(() => {
|
||||
// TODO(vicb): expect a warning message when we have a proper log service
|
||||
it('should throw if trackBy is not a function', async(() => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></template>`;
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.value = 0;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
|
||||
// TODO(vicb): expect no warning message when we have a proper log service
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.componentInstance.value = null;
|
||||
detectChangesAndExpectText('abc');
|
||||
fixture.componentInstance.value = undefined;
|
||||
detectChangesAndExpectText('abc');
|
||||
getComponent().items = [{id: 1}, {id: 2}];
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(/trackBy must be a function, but received null/);
|
||||
}));
|
||||
|
||||
it('should set the context to the component instance', async(() => {
|
||||
@ -354,7 +343,6 @@ export function main() {
|
||||
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
||||
detectChangesAndExpectText('red');
|
||||
}));
|
||||
|
||||
it('should move items around and keep them updated ', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
@ -390,7 +378,6 @@ class Foo {
|
||||
@Component({selector: 'test-cmp', template: ''})
|
||||
class TestComponent {
|
||||
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
|
||||
value: any;
|
||||
items: any[] = [1, 2];
|
||||
trackById(index: number, item: any): string { return item['id']; }
|
||||
trackByIndex(index: number, item: any): number { return index; }
|
||||
@ -407,4 +394,4 @@ const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString(
|
||||
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.createComponent(TestComponent);
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +153,78 @@ export function main() {
|
||||
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
||||
.toBe(true);
|
||||
}));
|
||||
|
||||
describe('else', () => {
|
||||
it('should support else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
|
||||
'<template #elseBlock>FALSE</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE');
|
||||
}));
|
||||
|
||||
it('should support then and else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
|
||||
'<template #thenBlock>THEN</template>' +
|
||||
'<template #elseBlock>ELSE</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('THEN');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('ELSE');
|
||||
}));
|
||||
|
||||
it('should support dynamic else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
|
||||
'<template #b1>FALSE1</template>' +
|
||||
'<template #b2>FALSE2</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE1');
|
||||
|
||||
getComponent().nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE2');
|
||||
}));
|
||||
|
||||
it('should support binding to variable', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
|
||||
'<template #elseBlock let-v>{{v}}</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('true');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('false');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new LowerCasePipe(); });
|
||||
|
||||
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
expect(pipe.transform('FOO')).toEqual('foo');
|
||||
expect(pipe.transform('BAr')).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('TitleCasePipe', () => {
|
||||
let pipe: TitleCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new TitleCasePipe(); });
|
||||
|
||||
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
|
||||
|
||||
it('should return titlecase for subsequent words',
|
||||
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
|
||||
|
||||
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
|
||||
|
||||
it('should persist whitespace',
|
||||
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
|
||||
|
||||
it('should titlecase when there is a new value', () => {
|
||||
expect(pipe.transform('bar')).toEqual('Bar');
|
||||
expect(pipe.transform('foo')).toEqual('Foo');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('UpperCasePipe', () => {
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new UpperCasePipe(); });
|
||||
|
||||
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
expect(pipe.transform('foo')).toEqual('FOO');
|
||||
expect(pipe.transform('bar')).toEqual('BAR');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new LowerCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
it('should return lowercase', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
});
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
const val2 = pipe.transform('WAT');
|
||||
expect(val2).toEqual('wat');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @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 {UpperCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('UpperCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new UpperCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
|
||||
it('should return uppercase', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
});
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
const val2 = pipe.transform('wat');
|
||||
expect(val2).toEqual('WAT');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -15,8 +15,6 @@
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": ".",
|
||||
"outDir": "../node_modules/third_party",
|
||||
// Prevent scanning up the directory tree for types
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
}
|
||||
"outDir": "../node_modules/third_party"
|
||||
}
|
||||
}
|
@ -14,9 +14,7 @@
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": ".",
|
||||
// Prevent scanning up the directory tree for types
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
"baseUrl": "."
|
||||
},
|
||||
|
||||
"files": [
|
||||
|
@ -11,6 +11,7 @@
|
||||
* Intended to be used in a build step.
|
||||
*/
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||
import {readFileSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
@ -18,6 +19,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {Console} from './private_import_core';
|
||||
|
||||
const GENERATED_META_FILES = /\.json$/;
|
||||
|
||||
|
@ -9,10 +9,10 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export type ReflectorReader = typeof r._ReflectorReader;
|
||||
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||
|
||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
|
||||
export type Console = typeof r._Console;
|
||||
export const Console: typeof r.Console = r.Console;
|
||||
export var Console: typeof r.Console = r.Console;
|
||||
|
@ -122,9 +122,11 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
return ts.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
|
||||
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); }
|
||||
|
||||
getCurrentDirectory(): string { return this.context.currentDirectory; }
|
||||
getCurrentDirectory(): string {
|
||||
return this.context.currentDirectory;
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
|
@ -9,5 +9,5 @@
|
||||
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;
|
||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export var reflector: typeof r.reflector = r.reflector;
|
||||
|
@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
import {Console, LifecycleHooks} from './private_import_core';
|
||||
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {BindingParser} from './template_parser/binding_parser';
|
||||
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
||||
|
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
|
||||
return new Token(index, TokenType.Error, 0, message);
|
||||
}
|
||||
|
||||
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||
|
||||
class _Scanner {
|
||||
length: number;
|
||||
|
@ -9,13 +9,13 @@
|
||||
import * as i18n from './i18n_ast';
|
||||
|
||||
export function digest(message: i18n.Message): string {
|
||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
return message.id || sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
}
|
||||
|
||||
export function decimalDigest(message: i18n.Message): string {
|
||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||
return computeMsgId(parts.join(''), message.meaning);
|
||||
return message.id || computeMsgId(parts.join(''), message.meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
|
||||
const _I18N_ATTR = 'i18n';
|
||||
const _I18N_ATTR_PREFIX = 'i18n-';
|
||||
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
|
||||
const MEANING_SEPARATOR = '|';
|
||||
const ID_SEPARATOR = '@@';
|
||||
|
||||
/**
|
||||
* Extract translatable messages from an html AST
|
||||
@ -77,7 +79,7 @@ class _Visitor implements html.Visitor {
|
||||
// _VisitorMode.Merge only
|
||||
private _translations: TranslationBundle;
|
||||
private _createI18nMessage:
|
||||
(msg: html.Node[], meaning: string, description: string) => i18n.Message;
|
||||
(msg: html.Node[], meaning: string, description: string, id: string) => i18n.Message;
|
||||
|
||||
|
||||
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
|
||||
@ -330,15 +332,15 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
// add a translatable message
|
||||
private _addMessage(ast: html.Node[], meaningAndDesc?: string): i18n.Message {
|
||||
private _addMessage(ast: html.Node[], msgMeta?: string): i18n.Message {
|
||||
if (ast.length == 0 ||
|
||||
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
|
||||
// Do not create empty messages
|
||||
return;
|
||||
}
|
||||
|
||||
const [meaning, description] = _splitMeaningAndDesc(meaningAndDesc);
|
||||
const message = this._createI18nMessage(ast, meaning, description);
|
||||
const {meaning, description, id} = _parseMessageMeta(msgMeta);
|
||||
const message = this._createI18nMessage(ast, meaning, description, id);
|
||||
this._messages.push(message);
|
||||
return message;
|
||||
}
|
||||
@ -368,7 +370,7 @@ class _Visitor implements html.Visitor {
|
||||
attributes.forEach(attr => {
|
||||
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
|
||||
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
|
||||
_splitMeaningAndDesc(attr.value)[0];
|
||||
_parseMessageMeta(attr.value).meaning;
|
||||
}
|
||||
});
|
||||
|
||||
@ -382,7 +384,7 @@ class _Visitor implements html.Visitor {
|
||||
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '', '');
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
@ -496,8 +498,16 @@ function _getI18nAttr(p: html.Element): html.Attribute {
|
||||
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
|
||||
}
|
||||
|
||||
function _splitMeaningAndDesc(i18n: string): [string, string] {
|
||||
if (!i18n) return ['', ''];
|
||||
const pipeIndex = i18n.indexOf('|');
|
||||
return pipeIndex == -1 ? ['', i18n] : [i18n.slice(0, pipeIndex), i18n.slice(pipeIndex + 1)];
|
||||
function _parseMessageMeta(i18n: string): {meaning: string, description: string, id: string} {
|
||||
if (!i18n) return {meaning: '', description: '', id: ''};
|
||||
|
||||
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
||||
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
||||
const [meaningAndDesc, id] =
|
||||
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
||||
const [meaning, description] = (descIndex > -1) ?
|
||||
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
||||
['', meaningAndDesc];
|
||||
|
||||
return {meaning, description, id};
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ export class Message {
|
||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||
* @param meaning
|
||||
* @param description
|
||||
* @param id
|
||||
*/
|
||||
constructor(
|
||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||
public description: string) {}
|
||||
public description: string, public id: string) {}
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
|
@ -22,11 +22,11 @@ const _expParser = new ExpressionParser(new ExpressionLexer());
|
||||
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
||||
*/
|
||||
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
|
||||
nodes: html.Node[], meaning: string, description: string) => i18n.Message {
|
||||
nodes: html.Node[], meaning: string, description: string, id: string) => i18n.Message {
|
||||
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
||||
|
||||
return (nodes: html.Node[], meaning: string, description: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description);
|
||||
return (nodes: html.Node[], meaning: string, description: string, id: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description, id);
|
||||
}
|
||||
|
||||
class _I18nVisitor implements html.Visitor {
|
||||
@ -40,7 +40,8 @@ class _I18nVisitor implements html.Visitor {
|
||||
private _expressionParser: ExpressionParser,
|
||||
private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string): i18n.Message {
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string, id: string):
|
||||
i18n.Message {
|
||||
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
|
||||
this._icuDepth = 0;
|
||||
this._placeholderRegistry = new PlaceholderRegistry();
|
||||
@ -50,7 +51,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||
|
||||
return new i18n.Message(
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description, id);
|
||||
}
|
||||
|
||||
visitElement(el: html.Element, context: any): i18n.Node {
|
||||
@ -115,7 +116,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
||||
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '', '');
|
||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||
}
|
||||
|
||||
|
@ -987,4 +987,4 @@ function stringifyType(type: any): string {
|
||||
} else {
|
||||
return stringify(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,13 +68,13 @@ export class MapType extends Type {
|
||||
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
||||
}
|
||||
|
||||
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||
|
||||
export interface TypeVisitor {
|
||||
visitBuiltintType(type: BuiltinType, context: any): any;
|
||||
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||
}
|
||||
|
||||
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||
export const NULL_EXPR = new LiteralExpr(null, null);
|
||||
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||
export var NULL_EXPR = new LiteralExpr(null, null);
|
||||
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||
|
||||
//// Statements
|
||||
export enum StmtModifier {
|
||||
|
@ -9,12 +9,12 @@
|
||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'g');
|
||||
|
||||
/**
|
||||
|
@ -149,7 +149,7 @@ export class TemplateParser {
|
||||
return new TemplateParseResult(result, errors);
|
||||
}
|
||||
|
||||
if (this.transforms) {
|
||||
if (isPresent(this.transforms)) {
|
||||
this.transforms.forEach(
|
||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||
}
|
||||
@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
visitText(text: html.Text, parent: ElementContext): any {
|
||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
|
||||
if (expr) {
|
||||
if (isPresent(expr)) {
|
||||
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
|
||||
} else {
|
||||
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
|
||||
@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchableAttrs: [string, string][] = [];
|
||||
const matchableAttrs: string[][] = [];
|
||||
const elementOrDirectiveProps: BoundProperty[] = [];
|
||||
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||
const elementVars: VariableAst[] = [];
|
||||
const events: BoundEventAst[] = [];
|
||||
|
||||
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
||||
const templateMatchableAttrs: [string, string][] = [];
|
||||
const templateMatchableAttrs: string[][] = [];
|
||||
const templateElementVars: VariableAst[] = [];
|
||||
|
||||
let hasInlineTemplates = false;
|
||||
@ -268,17 +268,14 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
|
||||
let templateBindingsSource: string|undefined;
|
||||
let prefixToken: string|undefined;
|
||||
let normalizedName = this._normalizeAttributeName(attr.name);
|
||||
|
||||
if (normalizedName == TEMPLATE_ATTR) {
|
||||
let templateBindingsSource: string|undefined = undefined;
|
||||
let prefixToken: string|undefined = undefined;
|
||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
templateBindingsSource = attr.value;
|
||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
}
|
||||
|
||||
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||
if (hasTemplateBinding) {
|
||||
if (hasInlineTemplates) {
|
||||
@ -544,12 +541,10 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
||||
const matchedReferences = new Set<string>();
|
||||
let component: CompileDirectiveSummary = null;
|
||||
|
||||
const directiveAsts = directives.map((directive) => {
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
elementSourceSpan.start, elementSourceSpan.end,
|
||||
`Directive ${identifierName(directive.type)}`);
|
||||
|
||||
if (directive.isComponent) {
|
||||
component = directive;
|
||||
}
|
||||
@ -572,7 +567,6 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
return new DirectiveAst(
|
||||
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
||||
});
|
||||
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
if (elOrDirRef.value.length > 0) {
|
||||
if (!matchedReferences.has(elOrDirRef.name)) {
|
||||
@ -587,7 +581,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
|
||||
}
|
||||
});
|
||||
}); // fix syntax highlighting issue: `
|
||||
return directiveAsts;
|
||||
}
|
||||
|
||||
@ -748,7 +742,7 @@ class NonBindableVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
|
||||
const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
|
||||
const selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
@ -817,16 +811,16 @@ class ElementContext {
|
||||
}
|
||||
|
||||
export function createElementCssSelector(
|
||||
elementName: string, attributes: [string, string][]): CssSelector {
|
||||
elementName: string, matchableAttrs: string[][]): CssSelector {
|
||||
const cssSelector = new CssSelector();
|
||||
const elNameNoNs = splitNsName(elementName)[1];
|
||||
|
||||
cssSelector.setElement(elNameNoNs);
|
||||
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attrName = attributes[i][0];
|
||||
for (let i = 0; i < matchableAttrs.length; i++) {
|
||||
const attrName = matchableAttrs[i][0];
|
||||
const attrNameNoNs = splitNsName(attrName)[1];
|
||||
const attrValue = attributes[i][1];
|
||||
const attrValue = matchableAttrs[i][1];
|
||||
|
||||
cssSelector.addAttribute(attrNameNoNs, attrValue);
|
||||
if (attrName.toLowerCase() == CLASS_ATTR) {
|
||||
|
@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
|
||||
/**
|
||||
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
|
||||
*/
|
||||
export const DEFAULT_PACKAGE_URL_PROVIDER = {
|
||||
export var DEFAULT_PACKAGE_URL_PROVIDER = {
|
||||
provide: PACKAGE_ROOT_URL,
|
||||
useValue: '/'
|
||||
};
|
||||
|
@ -211,7 +211,12 @@ export class CompileElement extends CompileNode {
|
||||
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
|
||||
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
|
||||
}
|
||||
|
||||
const queriesWithReads: _QueryWithRead[] = [];
|
||||
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
|
||||
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
|
||||
queriesWithReads.push(
|
||||
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
|
||||
});
|
||||
Object.keys(this.referenceTokens).forEach(varName => {
|
||||
const token = this.referenceTokens[varName];
|
||||
let varValue: o.Expression;
|
||||
@ -221,6 +226,27 @@ export class CompileElement extends CompileNode {
|
||||
varValue = this.renderNode;
|
||||
}
|
||||
this.view.locals.set(varName, varValue);
|
||||
const varToken = {value: varName};
|
||||
queriesWithReads.push(
|
||||
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
let value: o.Expression;
|
||||
if (isPresent(queryWithRead.read.identifier)) {
|
||||
// query for an identifier
|
||||
value = this.instances.get(tokenReference(queryWithRead.read));
|
||||
} else {
|
||||
// query for a reference
|
||||
const token = this.referenceTokens[queryWithRead.read.value];
|
||||
if (isPresent(token)) {
|
||||
value = this.instances.get(tokenReference(token));
|
||||
} else {
|
||||
value = this.elementRef;
|
||||
}
|
||||
}
|
||||
if (isPresent(value)) {
|
||||
queryWithRead.query.addValue(value, this.view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -239,14 +265,12 @@ export class CompileElement extends CompileNode {
|
||||
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
|
||||
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
||||
});
|
||||
}
|
||||
|
||||
finish() {
|
||||
Array.from(this._queries.values())
|
||||
.forEach(
|
||||
queries => queries.forEach(
|
||||
q => q.generateStatements(
|
||||
this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||
q =>
|
||||
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||
}
|
||||
|
||||
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
||||
@ -259,11 +283,12 @@ export class CompileElement extends CompileNode {
|
||||
null;
|
||||
}
|
||||
|
||||
getProviderTokens(): CompileTokenMetadata[] {
|
||||
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
|
||||
getProviderTokens(): o.Expression[] {
|
||||
return Array.from(this._resolvedProviders.values())
|
||||
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
|
||||
}
|
||||
|
||||
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||
const result: CompileQuery[] = [];
|
||||
let currentEl: CompileElement = this;
|
||||
let distance = 0;
|
||||
@ -401,3 +426,10 @@ function createProviderProperty(
|
||||
}
|
||||
return o.THIS_EXPR.prop(propName);
|
||||
}
|
||||
|
||||
class _QueryWithRead {
|
||||
public read: CompileTokenMetadata;
|
||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||
this.read = query.meta.read || match;
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export class CompileQuery {
|
||||
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
||||
}
|
||||
|
||||
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
const values = createQueryValues(this._values);
|
||||
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
||||
if (isPresent(this.ownerDirectiveExpression)) {
|
||||
|
@ -154,11 +154,11 @@ export class CompileView implements NameResolver {
|
||||
}
|
||||
}
|
||||
|
||||
finish() {
|
||||
afterNodes() {
|
||||
Array.from(this.viewQueries.values())
|
||||
.forEach(
|
||||
queries => queries.forEach(
|
||||
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
|
||||
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,4 +133,4 @@ type EventSummary = {
|
||||
name: string,
|
||||
target: string,
|
||||
phase: string
|
||||
};
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @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 {CompileQueryMetadata, CompileTokenMetadata, tokenReference} from '../compile_metadata';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileQuery} from './compile_query';
|
||||
|
||||
|
||||
// Note: We can't do this when we create the CompileElements already,
|
||||
// as we create embedded views before the <template> elements themselves.
|
||||
export function bindQueryValues(ce: CompileElement) {
|
||||
const queriesWithReads: _QueryWithRead[] = [];
|
||||
ce.getProviderTokens().forEach((token) => {
|
||||
const queriesForProvider = ce.getQueriesFor(token);
|
||||
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
|
||||
});
|
||||
Object.keys(ce.referenceTokens).forEach(varName => {
|
||||
const token = ce.referenceTokens[varName];
|
||||
const varToken = {value: varName};
|
||||
queriesWithReads.push(
|
||||
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
let value: o.Expression;
|
||||
if (queryWithRead.read.identifier) {
|
||||
// query for an identifier
|
||||
value = ce.instances.get(tokenReference(queryWithRead.read));
|
||||
} else {
|
||||
// query for a reference
|
||||
const token = ce.referenceTokens[queryWithRead.read.value];
|
||||
if (token) {
|
||||
value = ce.instances.get(tokenReference(token));
|
||||
} else {
|
||||
value = ce.elementRef;
|
||||
}
|
||||
}
|
||||
if (value) {
|
||||
queryWithRead.query.addValue(value, ce.view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class _QueryWithRead {
|
||||
public read: CompileTokenMetadata;
|
||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||
this.read = query.meta.read || match;
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ import {CompileView} from './compile_view';
|
||||
import {bindOutputs} from './event_binder';
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||
import {bindQueryValues} from './query_binder';
|
||||
|
||||
export function bindView(
|
||||
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
||||
@ -44,7 +43,6 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
bindQueryValues(compileElement);
|
||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
@ -77,7 +75,6 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
bindQueryValues(compileElement);
|
||||
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
|
@ -48,16 +48,13 @@ export function buildView(
|
||||
}
|
||||
|
||||
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
||||
view.afterNodes();
|
||||
createViewTopLevelStmts(view, targetStatements);
|
||||
view.nodes.forEach((node) => {
|
||||
if (node instanceof CompileElement) {
|
||||
node.finish();
|
||||
if (node.hasEmbeddedView) {
|
||||
finishView(node.embeddedView, targetStatements);
|
||||
}
|
||||
if (node instanceof CompileElement && node.hasEmbeddedView) {
|
||||
finishView(node.embeddedView, targetStatements);
|
||||
}
|
||||
});
|
||||
view.finish();
|
||||
createViewTopLevelStmts(view, targetStatements);
|
||||
}
|
||||
|
||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
@ -421,9 +418,7 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
|
||||
let componentToken: o.Expression = o.NULL_EXPR;
|
||||
const varTokenEntries: any[] = [];
|
||||
if (isPresent(compileElement)) {
|
||||
providerTokens =
|
||||
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
|
||||
|
||||
providerTokens = compileElement.getProviderTokens();
|
||||
if (isPresent(compileElement.component)) {
|
||||
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
||||
}
|
||||
|
@ -6,10 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {computeMsgId, sha1} from '../../src/i18n/digest';
|
||||
import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
|
||||
export function main(): void {
|
||||
describe('digest', () => {
|
||||
describe('digest', () => {
|
||||
it('must return the ID if it\'s explicit', () => {
|
||||
expect(digest({
|
||||
id: 'i',
|
||||
nodes: [],
|
||||
placeholders: {},
|
||||
placeholderToMessage: {},
|
||||
meaning: '',
|
||||
description: '',
|
||||
})).toEqual('i');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sha1', () => {
|
||||
it('should work on empty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
|
@ -20,7 +20,10 @@ export function main() {
|
||||
describe('elements', () => {
|
||||
it('should extract from elements', () => {
|
||||
expect(extract('<div i18n="m|d|e">text<span>nested</span></div>')).toEqual([
|
||||
[['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e'],
|
||||
[
|
||||
['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e',
|
||||
''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -29,11 +32,45 @@ export function main() {
|
||||
extract(
|
||||
'<div i18n="m1|d1"><span i18n-title="m2|d2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1'],
|
||||
[['single child'], 'm2', 'd2'],
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1', ''],
|
||||
[['single child'], 'm2', 'd2', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m1|d1@@i1"><span i18n-title="m2|d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[
|
||||
['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1',
|
||||
'i1'
|
||||
],
|
||||
[['single child'], 'm2', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes without meaning and with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="d1@@i1"><span i18n-title="d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', 'd1', 'i1'],
|
||||
[['single child'], '', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id only', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="@@i1"><span i18n-title="@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', '', 'i1'],
|
||||
[['single child'], '', '', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should extract from ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
@ -43,10 +80,10 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', ''
|
||||
],
|
||||
[['title'], '', ''],
|
||||
[['desc'], '', ''],
|
||||
[['title'], '', '', ''],
|
||||
[['desc'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -55,7 +92,7 @@ export function main() {
|
||||
|
||||
it('should ignore implicit elements in translatable elements', () => {
|
||||
expect(extract('<div i18n="m|d"><p></p></div>', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -64,17 +101,19 @@ export function main() {
|
||||
it('should extract from blocks', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 -->message1<!-- /i18n -->
|
||||
<!-- i18n: desc2 -->message2<!-- /i18n -->
|
||||
<!-- i18n -->message3<!-- /i18n -->`))
|
||||
<!-- i18n -->message3<!-- /i18n -->
|
||||
<!-- i18n: meaning4|desc4@@id4 -->message4<!-- /i18n -->
|
||||
<!-- i18n: @@id5 -->message5<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message2'], '', 'desc2'],
|
||||
[['message3'], '', ''],
|
||||
[['message1'], 'meaning1', 'desc1', ''], [['message2'], '', 'desc2', ''],
|
||||
[['message3'], '', '', ''], [['message4'], 'meaning4', 'desc4', 'id4'],
|
||||
[['message5'], '', '', 'id5']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in blocks', () => {
|
||||
expect(extract('<!-- i18n:m|d --><p></p><!-- /i18n -->', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
|
||||
@ -88,7 +127,7 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -98,15 +137,15 @@ export function main() {
|
||||
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
'[<ph name="INTERPOLATION">interp</ph>]'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore other comments', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
expect(extract(`<!-- i18n: meaning1|desc1@@id1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message1'], 'meaning1', 'desc1', 'id1'],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -118,34 +157,37 @@ export function main() {
|
||||
it('should extract ICU messages from translatable elements', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 {text}}</div>')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// single message when ICU is the only (implicit) children
|
||||
expect(extract('<div>{count, plural, =0 {text}}</div>', ['div'])).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
|
||||
// one message for the element content and one message for the ICU
|
||||
expect(extract('<div i18n="m|d">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
expect(extract('<div i18n="m|d@@i">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd',
|
||||
'i'
|
||||
],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract ICU messages from translatable block', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<!-- i18n:m|d -->{count, plural, =0 {text}}<!-- /i18n -->')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// one message for the block content and one message for the ICU
|
||||
expect(extract('<!-- i18n:m|d -->before{count, plural, =0 {text}}after<!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm',
|
||||
'd'
|
||||
'd', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -156,20 +198,20 @@ export function main() {
|
||||
it('should ignore nested ICU messages', () => {
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 { {sex, select, male {m}} }}</div>'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in non translatable ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m|d">{count, plural, =0 { {sex, select, male {<p>ignore</p>}} }}</div>',
|
||||
['p']))
|
||||
expect(extract(
|
||||
'<div i18n="m|d@@i">{count, plural, =0 { {sex, select, male {<p>ignore</p>}}' +
|
||||
' }}</div>',
|
||||
['p']))
|
||||
.toEqual([[
|
||||
[
|
||||
'{count, plural, =0 {[{sex, select, male {[<ph tag name="START_PARAGRAPH">ignore</ph name="CLOSE_PARAGRAPH">]}}, ]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', 'i'
|
||||
]]);
|
||||
});
|
||||
|
||||
@ -181,46 +223,45 @@ export function main() {
|
||||
|
||||
describe('attributes', () => {
|
||||
it('should extract from attributes outside of translatable sections', () => {
|
||||
expect(extract('<div i18n-title="m|d" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
expect(extract('<div i18n-title="m|d@@i" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable elements', () => {
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d" title="msg"></b></p></div>')).toEqual([
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d@@i" title="msg"></b></p></div>')).toEqual([
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable blocks', () => {
|
||||
expect(extract('<!-- i18n --><p><b i18n-title="m|d" title="msg"></b></p><!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable ICUs', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}<!-- /i18n -->'))
|
||||
expect(extract(`<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d@@i"
|
||||
title="msg"></b></p>}}<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
[
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag' +
|
||||
' name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -228,7 +269,7 @@ export function main() {
|
||||
it('should extract from attributes in non translatable ICUs', () => {
|
||||
expect(extract('{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -239,7 +280,7 @@ export function main() {
|
||||
describe('implicit elements', () => {
|
||||
it('should extract from implicit elements', () => {
|
||||
expect(extract('<b>bold</b><i>italic</i>', ['b'])).toEqual([
|
||||
[['bold'], '', ''],
|
||||
[['bold'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -251,7 +292,7 @@ export function main() {
|
||||
}).not.toThrow();
|
||||
|
||||
expect(result).toEqual([
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', ''],
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -261,7 +302,7 @@ export function main() {
|
||||
it('should extract implicit attributes', () => {
|
||||
expect(extract('<b title="bb">bold</b><i title="ii">italic</i>', [], {'b': ['title']}))
|
||||
.toEqual([
|
||||
[['bb'], '', ''],
|
||||
[['bb'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -433,7 +474,7 @@ function extract(
|
||||
// clang-format off
|
||||
// https://github.com/angular/clang-format/issues/35
|
||||
return result.messages.map(
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][];
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, message.id]) as [string[], string, string][];
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
@ -59,14 +59,17 @@ export function main() {
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('un');
|
||||
cmp.count = 2;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('deux');
|
||||
cmp.count = 3;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('beaucoup');
|
||||
|
||||
cmp.sex = 'm';
|
||||
cmp.sexB = 'f';
|
||||
@ -90,8 +93,8 @@ export function main() {
|
||||
.toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
|
||||
expectHtml(el, '#i18n-13')
|
||||
.toBe('<div id="i18n-13" title="dans une section traductible"></div>');
|
||||
|
||||
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
|
||||
expectHtml(el, '#i18n-16').toMatch(/avec un ID explicite/);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -141,6 +144,8 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
<div id="i18n-16" i18n="@@i18n16">with an explicit ID</div>
|
||||
<div id="i18n-17" i18n="@@i18n17">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
@ -182,6 +187,9 @@ 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>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@ -210,5 +218,7 @@ const XMB = `
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><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>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -18,6 +18,8 @@ const HTML = `
|
||||
<p i18n-title title="translatable attribute">not translatable</p>
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="ph names"><br><img><div></div></p>
|
||||
`;
|
||||
|
||||
@ -39,6 +41,16 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" 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/>
|
||||
@ -67,6 +79,16 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>toto</target>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>tata</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" 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><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>
|
||||
@ -107,6 +129,8 @@ export function main(): void {
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'i': 'toto',
|
||||
'bar': 'tata',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
});
|
||||
|
@ -18,6 +18,9 @@ export function main(): void {
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<!-- i18n -->{ count, plural, =0 {<p>test</p>}}<!-- /i18n -->
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="@@baz">{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
|
||||
<p i18n>{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>`;
|
||||
|
||||
const XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
@ -46,6 +49,9 @@ export function main(): void {
|
||||
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex><b></ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex><p></ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} }</msg>
|
||||
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
|
||||
<msg id="i" desc="d" meaning="m">foo</msg>
|
||||
<msg id="bar">foo</msg>
|
||||
<msg id="baz">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -21,7 +21,7 @@ export function main(): void {
|
||||
it('should translate a plain message', () => {
|
||||
const msgMap = {foo: [new i18n.Text('bar', null)]};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ export function main(): void {
|
||||
ph1: '*phContent*',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']);
|
||||
});
|
||||
|
||||
@ -51,8 +51,8 @@ export function main(): void {
|
||||
new i18n.Text('*refMsg*', null),
|
||||
],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -69,13 +69,13 @@ export function main(): void {
|
||||
]
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/);
|
||||
});
|
||||
|
||||
it('should report missing translation', () => {
|
||||
const tb = new TranslationBundle({}, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message foo/);
|
||||
});
|
||||
|
||||
@ -83,8 +83,8 @@ export function main(): void {
|
||||
const msgMap = {
|
||||
foo: [new i18n.Placeholder('', 'ph1', span)],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -102,7 +102,7 @@ export function main(): void {
|
||||
ph1: '</b>',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/);
|
||||
});
|
||||
});
|
||||
|
@ -1,51 +0,0 @@
|
||||
/**
|
||||
* @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 {Component, Directive, Input} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
|
||||
|
||||
describe('directives', () => {
|
||||
it('should support dotted selectors', async(() => {
|
||||
@Directive({selector: '[dot.name]'})
|
||||
class MyDir {
|
||||
@Input('dot.name') value: string;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MyDir,
|
||||
TestComponent,
|
||||
],
|
||||
});
|
||||
|
||||
const template = `<div [dot.name]="'foo'"></div>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
|
||||
expect(myDir.value).toEqual('foo');
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmp', template: ''})
|
||||
class TestComponent {
|
||||
}
|
||||
|
||||
function createTestComponent(template: string): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.createComponent(TestComponent);
|
||||
}
|
@ -44,7 +44,7 @@ const baseErrorIdentifier = {
|
||||
runtime: BaseError
|
||||
};
|
||||
|
||||
export const codegenExportsVars = [
|
||||
export var codegenExportsVars = [
|
||||
'getExpressions',
|
||||
];
|
||||
|
||||
@ -190,7 +190,7 @@ const _getExpressionsStmts: o.Statement[] = [
|
||||
]))
|
||||
];
|
||||
|
||||
export const codegenStmts: o.Statement[] = [
|
||||
export var codegenStmts: o.Statement[] = [
|
||||
new o.CommentStmt('This is a comment'),
|
||||
|
||||
new o.ClassStmt(
|
||||
|
@ -30,14 +30,14 @@ export function main() {
|
||||
it('should select by element name case sensitive', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
|
||||
|
||||
expect(matcher.match(getSelectorFor({tag: 'SOMEOTHERTAG'}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(getSelectorFor({tag: 'SOMETAG'}), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(getSelectorFor({tag: 'someTag'}), selectableCollector)).toEqual(true);
|
||||
expect(matcher.match(CssSelector.parse('someTag')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
|
||||
@ -45,22 +45,21 @@ export function main() {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
|
||||
|
||||
expect(matcher.match(getSelectorFor({classes: 'SOMEOTHERCLASS'}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(getSelectorFor({classes: 'SOMECLASS'}), selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(getSelectorFor({classes: 'someClass class2'}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
});
|
||||
|
||||
it('should not throw for class name "constructor"', () => {
|
||||
expect(matcher.match(getSelectorFor({classes: 'constructor'}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
});
|
||||
@ -69,43 +68,36 @@ export function main() {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
|
||||
expect(matcher.match(getSelectorFor({attrs: [['SOMEOTHERATTR', '']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(getSelectorFor({attrs: [['SOMEATTR', '']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(
|
||||
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'someValue']]}), selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(
|
||||
matcher.match(
|
||||
getSelectorFor({attrs: [['someAttr', ''], ['someAttr2', '']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(
|
||||
getSelectorFor({attrs: [['someAttr', 'someValue'], ['someAttr2', '']]}),
|
||||
selectableCollector))
|
||||
CssSelector.parse('[someAttr=someValue][someAttr2]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(
|
||||
getSelectorFor({attrs: [['someAttr2', ''], ['someAttr', 'someValue']]}),
|
||||
selectableCollector))
|
||||
CssSelector.parse('[someAttr2][someAttr=someValue]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(
|
||||
getSelectorFor({attrs: [['someAttr2', 'someValue'], ['someAttr', '']]}),
|
||||
selectableCollector))
|
||||
CssSelector.parse('[someAttr2=someValue][someAttr]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
});
|
||||
@ -113,13 +105,11 @@ export function main() {
|
||||
it('should support "." in attribute names', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
|
||||
|
||||
expect(matcher.match(getSelectorFor({attrs: [['barfoo', '']]}), selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(getSelectorFor({attrs: [['foo.bar', '']]}), selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
|
||||
@ -137,18 +127,15 @@ export function main() {
|
||||
it('should select by attr name case sensitive and value case insensitive', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor({attrs: [['SOMEATTR', 'SOMEOTHERATTR']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(
|
||||
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'SOMEVALUE']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(
|
||||
matcher.match(getSelectorFor({attrs: [['someAttr', 'SOMEVALUE']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('[someAttr=SOMEVALUE]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
@ -156,38 +143,31 @@ export function main() {
|
||||
it('should select by element name, class name and attribute name with value', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||
|
||||
expect(matcher.match(
|
||||
CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0],
|
||||
selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(
|
||||
matcher.match(
|
||||
getSelectorFor(
|
||||
{tag: 'someOtherTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
|
||||
selectableCollector))
|
||||
CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor(
|
||||
{tag: 'someTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
|
||||
selectableCollector))
|
||||
CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor(
|
||||
{tag: 'someTag', classes: 'someClass', attrs: [['someOtherAttr', '']]}),
|
||||
selectableCollector))
|
||||
expect(
|
||||
matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor({tag: 'someTag', classes: 'someClass', attrs: [['someAttr', '']]}),
|
||||
selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor(
|
||||
{tag: 'someTag', classes: 'someClass', attrs: [['someAttr', 'someValue']]}),
|
||||
selectableCollector))
|
||||
expect(
|
||||
matcher.match(
|
||||
CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
@ -237,9 +217,7 @@ export function main() {
|
||||
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
|
||||
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
|
||||
|
||||
expect(matcher.match(
|
||||
getSelectorFor({tag: 'p', classes: 'someClass', attrs: [['someAttr', '']]}),
|
||||
selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
});
|
||||
@ -250,38 +228,32 @@ export function main() {
|
||||
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
|
||||
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
|
||||
|
||||
expect(
|
||||
matcher.match(
|
||||
getSelectorFor({tag: 'p', attrs: [['someOtherAttr', '']], classes: 'someOtherClass'}),
|
||||
selectableCollector))
|
||||
expect(matcher.match(
|
||||
CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
||||
});
|
||||
|
||||
it('should match * with :not selector', () => {
|
||||
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
|
||||
expect(matcher.match(getSelectorFor({tag: 'div'}), () => {})).toEqual(true);
|
||||
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true);
|
||||
});
|
||||
|
||||
it('should match with multiple :not selectors', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
|
||||
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['a', '']]}), selectableCollector))
|
||||
.toBe(false);
|
||||
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['b', '']]}), selectableCollector))
|
||||
.toBe(false);
|
||||
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['c', '']]}), selectableCollector))
|
||||
.toBe(true);
|
||||
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
|
||||
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
|
||||
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
|
||||
});
|
||||
|
||||
it('should select with one match in a list', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
||||
|
||||
expect(matcher.match(getSelectorFor({tag: 'textbox'}), selectableCollector)).toEqual(true);
|
||||
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[1], 1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(
|
||||
getSelectorFor({tag: 'input', attrs: [['type', 'text']]}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
@ -289,8 +261,7 @@ export function main() {
|
||||
it('should not select twice with two matches in a list', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
|
||||
|
||||
expect(
|
||||
matcher.match(getSelectorFor({tag: 'input', classes: 'someclass'}), selectableCollector))
|
||||
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector))
|
||||
.toEqual(true);
|
||||
expect(matched.length).toEqual(2);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
@ -433,16 +404,3 @@ export function main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectorFor(
|
||||
{tag = '', attrs = [], classes = ''}: {tag?: string, attrs?: any[], classes?: string} = {}):
|
||||
CssSelector {
|
||||
const selector = new CssSelector();
|
||||
selector.setElement(tag);
|
||||
|
||||
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
|
||||
|
||||
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
|
||||
|
||||
return selector;
|
||||
}
|
@ -623,23 +623,6 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse directive dotted properties', () => {
|
||||
const dirA =
|
||||
CompileDirectiveMetadata
|
||||
.create({
|
||||
selector: '[dot.name]',
|
||||
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
|
||||
inputs: ['localName: dot.name'],
|
||||
})
|
||||
.toSummary();
|
||||
|
||||
expect(humanizeTplAst(parse('<div [dot.name]="expr"></div>', [dirA]))).toEqual([
|
||||
[ElementAst, 'div'],
|
||||
[DirectiveAst, dirA],
|
||||
[BoundDirectivePropertyAst, 'localName', 'expr'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should locate directives in property bindings', () => {
|
||||
const dirA =
|
||||
CompileDirectiveMetadata
|
||||
@ -1261,15 +1244,8 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
||||
});
|
||||
|
||||
it('should parse variables via let ...', () => {
|
||||
const targetAst = [
|
||||
[EmbeddedTemplateAst],
|
||||
[VariableAst, 'a', 'b'],
|
||||
[ElementAst, 'div'],
|
||||
];
|
||||
|
||||
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', []))).toEqual(targetAst);
|
||||
|
||||
expect(humanizeTplAst(parse('<div data-*ngIf="let a=b">', []))).toEqual(targetAst);
|
||||
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', [
|
||||
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
|
||||
});
|
||||
|
||||
describe('directives', () => {
|
||||
|
@ -10,12 +10,14 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export type ViewMetadata = typeof r._ViewMetadata;
|
||||
export const ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
|
||||
export var ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
|
||||
|
||||
|
||||
|
||||
import {__core_private_testing__ as r2} from '@angular/core/testing';
|
||||
|
||||
export type TestingCompiler = typeof r2._TestingCompiler;
|
||||
export const TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
|
||||
export var TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
|
||||
|
||||
export type TestingCompilerFactory = typeof r2._TestingCompilerFactory;
|
||||
export const TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;
|
||||
export var TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;
|
||||
|
@ -18,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
|
||||
// This provider is put here just so that we can access it from multiple
|
||||
// internal test packages.
|
||||
// TODO: get rid of it or move to a separate @angular/internal_testing package
|
||||
export const TEST_COMPILER_PROVIDERS: Provider[] = [
|
||||
export var TEST_COMPILER_PROVIDERS: Provider[] = [
|
||||
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
|
||||
{provide: ResourceLoader, useClass: MockResourceLoader},
|
||||
{provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix}
|
||||
|
@ -11,7 +11,6 @@ import {ApplicationInitStatus} from './application_init';
|
||||
import {ApplicationRef, ApplicationRef_} from './application_ref';
|
||||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
||||
import {Inject, Optional, SkipSelf} from './di/metadata';
|
||||
import {LOCALE_ID} from './i18n/tokens';
|
||||
import {Compiler} from './linker/compiler';
|
||||
import {ViewUtils} from './linker/view_utils';
|
||||
@ -25,10 +24,6 @@ export function _keyValueDiffersFactory() {
|
||||
return defaultKeyValueDiffers;
|
||||
}
|
||||
|
||||
export function _localeFactory(locale?: string): string {
|
||||
return locale || 'en-US';
|
||||
}
|
||||
|
||||
/**
|
||||
* This module includes the providers of @angular/core that are needed
|
||||
* to bootstrap components via `ApplicationRef`.
|
||||
@ -46,11 +41,7 @@ export function _localeFactory(locale?: string): string {
|
||||
AnimationQueue,
|
||||
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
|
||||
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useFactory: _localeFactory,
|
||||
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
|
||||
},
|
||||
{provide: LOCALE_ID, useValue: 'en-US'},
|
||||
]
|
||||
})
|
||||
export class ApplicationModule {
|
||||
|
@ -469,7 +469,7 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
this._loadComponent(compRef);
|
||||
if (isDevMode()) {
|
||||
this._console.log(
|
||||
`Angular is running in the development mode. Call enableProdMode() to enable the production mode.`);
|
||||
`Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.`);
|
||||
}
|
||||
return compRef;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ import * as api from './render/api';
|
||||
import * as decorators from './util/decorators';
|
||||
import {isPromise} from './util/lang';
|
||||
|
||||
export const __core_private__: {
|
||||
export var __core_private__: {
|
||||
isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy,
|
||||
ChangeDetectorStatus: typeof constants.ChangeDetectorStatus,
|
||||
_ChangeDetectorStatus?: constants.ChangeDetectorStatus,
|
||||
|
@ -225,33 +225,7 @@ export const ContentChildren: ContentChildrenDecorator =
|
||||
*/
|
||||
export interface ContentChildDecorator {
|
||||
/**
|
||||
* @whatItDoes Configures a content query.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* You can use ContentChild to get the first element or the directive matching the selector from
|
||||
* the content DOM. If the content DOM changes, and a new child matches the selector,
|
||||
* the property will be updated.
|
||||
*
|
||||
* Content queries are set before the `ngAfterContentInit` callback is called.
|
||||
*
|
||||
* **Metadata Properties**:
|
||||
*
|
||||
* * **selector** - the directive type or the name used for querying.
|
||||
* * **read** - read a different token from the queried element.
|
||||
*
|
||||
* Let's look at an example:
|
||||
*
|
||||
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
|
||||
*
|
||||
* **npm package**: `@angular/core`
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
* @docsNotRequired
|
||||
*/
|
||||
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
|
||||
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ContentChild;
|
||||
@ -267,7 +241,30 @@ export interface ContentChildDecorator {
|
||||
export type ContentChild = Query;
|
||||
|
||||
/**
|
||||
* ContentChild decorator and metadata.
|
||||
* @whatItDoes Configures a content query.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* You can use ContentChild to get the first element or the directive matching the selector from the
|
||||
* content DOM. If the content DOM changes, and a new child matches the selector,
|
||||
* the property will be updated.
|
||||
*
|
||||
* Content queries are set before the `ngAfterContentInit` callback is called.
|
||||
*
|
||||
* **Metadata Properties**:
|
||||
*
|
||||
* * **selector** - the directive type or the name used for querying.
|
||||
* * **read** - read a different token from the queried element.
|
||||
*
|
||||
* Let's look at an example:
|
||||
*
|
||||
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
|
||||
*
|
||||
* **npm package**: `@angular/core`
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
@ -293,35 +290,8 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
|
||||
*/
|
||||
export interface ViewChildrenDecorator {
|
||||
/**
|
||||
* @whatItDoes Configures a view query.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
|
||||
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
|
||||
* and the changes observable of the query list will emit a new value.
|
||||
*
|
||||
* View queries are set before the `ngAfterViewInit` callback is called.
|
||||
*
|
||||
* **Metadata Properties**:
|
||||
*
|
||||
* * **selector** - the directive type or the name used for querying.
|
||||
* * **read** - read a different token from the queried elements.
|
||||
*
|
||||
* Let's look at an example:
|
||||
*
|
||||
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
|
||||
*
|
||||
* **npm package**: `@angular/core`
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
*/
|
||||
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
|
||||
* @docsNotRequired
|
||||
*/ (selector: Type<any>|Function|string, {read}?: {read?: any}): any;
|
||||
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChildren;
|
||||
}
|
||||
|
||||
@ -333,7 +303,30 @@ export interface ViewChildrenDecorator {
|
||||
export type ViewChildren = Query;
|
||||
|
||||
/**
|
||||
* ViewChildren decorator and metadata.
|
||||
* @whatItDoes Configures a view query.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
|
||||
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
|
||||
* and the changes observable of the query list will emit a new value.
|
||||
*
|
||||
* View queries are set before the `ngAfterViewInit` callback is called.
|
||||
*
|
||||
* **Metadata Properties**:
|
||||
*
|
||||
* * **selector** - the directive type or the name used for querying.
|
||||
* * **read** - read a different token from the queried elements.
|
||||
*
|
||||
* Let's look at an example:
|
||||
*
|
||||
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
|
||||
*
|
||||
* **npm package**: `@angular/core`
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
|
@ -50,7 +50,7 @@ function noopScope(arg0?: any, arg1?: any): any {
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
|
||||
export var wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
|
||||
wtfEnabled ? createScope : (signature: string, flags?: any) => noopScope;
|
||||
|
||||
/**
|
||||
@ -62,7 +62,7 @@ export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
|
||||
* Returns the `returnValue for easy chaining.
|
||||
* @experimental
|
||||
*/
|
||||
export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
|
||||
export var wtfLeave: <T>(scope: any, returnValue?: T) => T =
|
||||
wtfEnabled ? leave : (s: any, r?: any) => r;
|
||||
|
||||
/**
|
||||
@ -78,7 +78,7 @@ export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
|
||||
* }
|
||||
* @experimental
|
||||
*/
|
||||
export const wtfStartTimeRange: (rangeType: string, action: string) => any =
|
||||
export var wtfStartTimeRange: (rangeType: string, action: string) => any =
|
||||
wtfEnabled ? startTimeRange : (rangeType: string, action: string) => null;
|
||||
|
||||
/**
|
||||
@ -87,4 +87,4 @@ export const wtfStartTimeRange: (rangeType: string, action: string) => any =
|
||||
* enabled.
|
||||
* @experimental
|
||||
*/
|
||||
export const wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;
|
||||
export var wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {global, isPresent, stringify} from '../facade/lang';
|
||||
import {Type, isType} from '../type';
|
||||
import {Type} from '../type';
|
||||
|
||||
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||
import {GetterFn, MethodFn, SetterFn} from './types';
|
||||
@ -105,10 +105,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
parameters(type: Type<any>): any[][] {
|
||||
// Note: only report metadata if we have at least one class decorator
|
||||
// to stay in sync with the static reflector.
|
||||
if (!isType(type)) {
|
||||
return [];
|
||||
}
|
||||
const parentCtor = getParentCtor(type);
|
||||
const parentCtor = Object.getPrototypeOf(type.prototype).constructor;
|
||||
let parameters = this._ownParameters(type, parentCtor);
|
||||
if (!parameters && parentCtor !== Object) {
|
||||
parameters = this.parameters(parentCtor);
|
||||
@ -138,10 +135,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
annotations(typeOrFunc: Type<any>): any[] {
|
||||
if (!isType(typeOrFunc)) {
|
||||
return [];
|
||||
}
|
||||
const parentCtor = getParentCtor(typeOrFunc);
|
||||
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
|
||||
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
|
||||
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
|
||||
return parentAnnotations.concat(ownAnnotations);
|
||||
@ -176,10 +170,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
|
||||
if (!isType(typeOrFunc)) {
|
||||
return {};
|
||||
}
|
||||
const parentCtor = getParentCtor(typeOrFunc);
|
||||
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
|
||||
const propMetadata: {[key: string]: any[]} = {};
|
||||
if (parentCtor !== Object) {
|
||||
const parentPropMetadata = this.propMetadata(parentCtor);
|
||||
@ -242,11 +233,3 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[]
|
||||
return new annotationCls(...annotationArgs);
|
||||
});
|
||||
}
|
||||
|
||||
function getParentCtor(ctor: Function): Type<any> {
|
||||
const parentProto = Object.getPrototypeOf(ctor.prototype);
|
||||
const parentCtor = parentProto ? parentProto.constructor : null;
|
||||
// Note: We always use `Object` as the null value
|
||||
// to simplify checking later on.
|
||||
return parentCtor || Object;
|
||||
}
|
||||
|
@ -18,8 +18,5 @@
|
||||
*/
|
||||
export const Type = Function;
|
||||
|
||||
export function isType(v: any): v is Type<any> {
|
||||
return typeof v === 'function';
|
||||
}
|
||||
|
||||
export interface Type<T> extends Function { new (...args: any[]): T; }
|
||||
|
@ -25,7 +25,6 @@ import {AnimationPlayer, NoOpAnimationPlayer} from '../../src/animation/animatio
|
||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event';
|
||||
import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
|
||||
import {Input} from '../../src/core';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '../../testing';
|
||||
import {MockAnimationPlayer} from '../../testing/mock_animation_player';
|
||||
@ -2244,52 +2243,38 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
if (!getDOM().supportsWebAnimation()) return;
|
||||
|
||||
it('should not throw an error when an animation exists within projected content that is not bound to the DOM',
|
||||
it('should recover if an animation driver or player throws an error during an animation',
|
||||
fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DummyIfCmp, DummyLoadingCmp],
|
||||
providers: [{provide: AnimationDriver, useClass: WebAnimationsDriver}],
|
||||
declarations: [DummyIfCmp],
|
||||
providers: [{provide: AnimationDriver, useClass: ErroneousAnimationDriver}],
|
||||
imports: [CommonModule]
|
||||
});
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<dummy-loading-cmp [exp2]="exp">
|
||||
<div [@myAnimation]="exp ? 'true' : 'false'" (@myAnimation.done)="callback()">world</div>
|
||||
</dummy-loading-cmp>
|
||||
<div [@myAnimation]="exp" (@myAnimation.start)="callback1($event)" (@myAnimation.done)="callback2($event)"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [transition(
|
||||
'* => *',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
animate(1000, style({transform: 'noooooo'})),
|
||||
])])]
|
||||
}
|
||||
});
|
||||
TestBed.overrideComponent(
|
||||
DummyLoadingCmp, {set: {template: `hello <ng-content *ngIf="exp2"></ng-content>`}});
|
||||
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
const container = fixture.nativeElement;
|
||||
let animationCalls = 0;
|
||||
cmp.callback = () => animationCalls++;
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(animationCalls).toBe(1);
|
||||
expect(getDOM().getText(container).trim()).toEqual('hello');
|
||||
|
||||
let started = false;
|
||||
let done = false;
|
||||
cmp.callback1 = (event: AnimationTransitionEvent) => started = true;
|
||||
cmp.callback2 = (event: AnimationTransitionEvent) => done = true;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(animationCalls).toBe(2);
|
||||
expect(getDOM().getText(container).trim()).toMatch(/hello[\s\r\n]+world/m);
|
||||
expect(started).toBe(true);
|
||||
expect(done).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
@ -2475,9 +2460,6 @@ class DummyIfCmp {
|
||||
class DummyLoadingCmp {
|
||||
exp: any = false;
|
||||
callback = () => {};
|
||||
|
||||
@Input('exp2')
|
||||
exp2: any = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -2589,3 +2571,11 @@ class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
class ErroneousAnimationDriver extends MockAnimationDriver {
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): WebAnimationsPlayer {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export function main() {
|
||||
{providers: [{provide: APP_INITIALIZER, multi: true, useValue: () => promise}]});
|
||||
});
|
||||
|
||||
it('should update the status once all async initializers are done',
|
||||
it('should updat the status once all async initializers are done',
|
||||
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
let completerResolver = false;
|
||||
setTimeout(() => {
|
||||
|
@ -14,4 +14,4 @@ export function main() {
|
||||
it('should set the default locale to "en-US"',
|
||||
inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); }));
|
||||
});
|
||||
}
|
||||
}
|
@ -43,8 +43,6 @@ export function main() {
|
||||
NeedsContentChildWithRead,
|
||||
NeedsViewChildrenWithRead,
|
||||
NeedsViewChildWithRead,
|
||||
NeedsContentChildTemplateRef,
|
||||
NeedsContentChildTemplateRefApp,
|
||||
NeedsViewContainerWithRead,
|
||||
ManualProjecting
|
||||
]
|
||||
@ -264,15 +262,6 @@ export function main() {
|
||||
expect(comp.textDirChild.text).toEqual('ca');
|
||||
});
|
||||
|
||||
it('should contain the first descendant content child templateRef', () => {
|
||||
const template = '<needs-content-child-template-ref-app>' +
|
||||
'</needs-content-child-template-ref-app>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
|
||||
view.detectChanges();
|
||||
expect(view.nativeElement).toHaveText('OUTER');
|
||||
});
|
||||
|
||||
it('should contain the first view child', () => {
|
||||
const template = '<needs-view-child-read></needs-view-child-read>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
@ -741,23 +730,6 @@ class NeedsContentChildWithRead {
|
||||
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar: TextDirective;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'needs-content-child-template-ref',
|
||||
template: '<div [ngTemplateOutlet]="templateRef"></div>'
|
||||
})
|
||||
class NeedsContentChildTemplateRef {
|
||||
@ContentChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'needs-content-child-template-ref-app',
|
||||
template: '<needs-content-child-template-ref>' +
|
||||
'<template>OUTER<template>INNER</template></template>' +
|
||||
'</needs-content-child-template-ref>'
|
||||
})
|
||||
class NeedsContentChildTemplateRefApp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'needs-view-children-read',
|
||||
template: '<div #q text="va"></div><div #w text="vb"></div>',
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
|
||||
import {Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
@ -119,7 +119,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(injector.get(token)).toEqual(tokenValue);
|
||||
});
|
||||
|
||||
it('should support providers with an anonymous function as token', () => {
|
||||
it('should support providers with an anonymous function', () => {
|
||||
const token = () => true;
|
||||
const tokenValue = 1;
|
||||
const injector = createInjector([{provide: token, useValue: tokenValue}]);
|
||||
@ -147,22 +147,6 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
const injector = createInjector([{provide: 'someToken', useValue: data}]);
|
||||
expect(injector.get('someToken')).toEqual(data);
|
||||
});
|
||||
|
||||
describe('ANALYZE_FOR_ENTRY_COMPONENTS providers', () => {
|
||||
|
||||
it('should support class instances', () => {
|
||||
class SomeObject {
|
||||
someMethod() {}
|
||||
}
|
||||
|
||||
expect(
|
||||
() => createInjector([
|
||||
{provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: new SomeObject(), multi: true}
|
||||
]))
|
||||
.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should allow logging a previous elements class binding via interpolation', () => {
|
||||
|
@ -190,8 +190,6 @@ export function main() {
|
||||
|
||||
class ChildNoDecorators extends Parent {}
|
||||
|
||||
class NoDecorators {}
|
||||
|
||||
// Check that metadata for Parent was not changed!
|
||||
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
|
||||
|
||||
@ -201,11 +199,6 @@ export function main() {
|
||||
|
||||
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
|
||||
{value: 'parent'})]);
|
||||
|
||||
expect(reflector.annotations(NoDecorators)).toEqual([]);
|
||||
expect(reflector.annotations(<any>{})).toEqual([]);
|
||||
expect(reflector.annotations(<any>1)).toEqual([]);
|
||||
expect(reflector.annotations(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should inherit parameters', () => {
|
||||
@ -233,8 +226,6 @@ export function main() {
|
||||
constructor(a: any, b: any, c: any) { super(null, null); }
|
||||
}
|
||||
|
||||
class NoDecorators {}
|
||||
|
||||
// Check that metadata for Parent was not changed!
|
||||
expect(reflector.parameters(Parent)).toEqual([
|
||||
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
|
||||
@ -251,11 +242,6 @@ export function main() {
|
||||
expect(reflector.parameters(ChildWithCtorNoDecorator)).toEqual([
|
||||
undefined, undefined, undefined
|
||||
]);
|
||||
|
||||
expect(reflector.parameters(NoDecorators)).toEqual([]);
|
||||
expect(reflector.parameters(<any>{})).toEqual([]);
|
||||
expect(reflector.parameters(<any>1)).toEqual([]);
|
||||
expect(reflector.parameters(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should inherit property metadata', () => {
|
||||
@ -277,8 +263,6 @@ export function main() {
|
||||
c: C;
|
||||
}
|
||||
|
||||
class NoDecorators {}
|
||||
|
||||
// Check that metadata for Parent was not changed!
|
||||
expect(reflector.propMetadata(Parent)).toEqual({
|
||||
'a': [new PropDecorator('a')],
|
||||
@ -290,11 +274,6 @@ export function main() {
|
||||
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||
'c': [new PropDecorator('c')]
|
||||
});
|
||||
|
||||
expect(reflector.propMetadata(NoDecorators)).toEqual({});
|
||||
expect(reflector.propMetadata(<any>{})).toEqual({});
|
||||
expect(reflector.propMetadata(<any>1)).toEqual({});
|
||||
expect(reflector.propMetadata(null)).toEqual({});
|
||||
});
|
||||
|
||||
it('should inherit lifecycle hooks', () => {
|
||||
@ -353,7 +332,7 @@ export function main() {
|
||||
static ctorParameters = () =>
|
||||
[{type: A, decorators: [{type: ParamDecorator, args: ['a']}]},
|
||||
{type: B, decorators: [{type: ParamDecorator, args: ['b']}]},
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
class Child extends Parent {}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import * as mock_animation_player from './mock_animation_player';
|
||||
import * as test_compiler from './test_compiler';
|
||||
|
||||
export const __core_private_testing__: {
|
||||
export var __core_private_testing__: {
|
||||
TestingCompiler: typeof test_compiler.TestingCompiler,
|
||||
_TestingCompiler?: test_compiler.TestingCompiler,
|
||||
TestingCompilerFactory: typeof test_compiler.TestingCompilerFactory,
|
||||
|
@ -135,6 +135,11 @@ export class TestBed implements Injector {
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
static overrideTemplate(component: Type<any>, template: string): typeof TestBed {
|
||||
getTestBed().overrideComponent(component, {set: {template, templateUrl: null}});
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
|
||||
return getTestBed().get(token, notFoundValue);
|
||||
}
|
||||
@ -262,7 +267,7 @@ export class TestBed implements Injector {
|
||||
} catch (e) {
|
||||
if (e.compType) {
|
||||
throw new Error(
|
||||
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
||||
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl", but they were never compiled. ` +
|
||||
`Please call "TestBed.compileComponents" before your test.`);
|
||||
} else {
|
||||
throw e;
|
||||
|
@ -29,4 +29,4 @@ if (_global.beforeEach) {
|
||||
|
||||
// TODO(juliemr): remove this, only used because we need to export something to have compilation
|
||||
// work.
|
||||
export const __core_private_testing_placeholder__ = '';
|
||||
export var __core_private_testing_placeholder__ = '';
|
||||
|
@ -19,12 +19,12 @@ export {inject} from './test_bed';
|
||||
export * from './logger';
|
||||
export * from './ng_zone_mock';
|
||||
|
||||
export const proxy: ClassDecorator = (t: any) => t;
|
||||
export var proxy: ClassDecorator = (t: any) => t;
|
||||
|
||||
const _global = <any>(typeof window === 'undefined' ? global : window);
|
||||
|
||||
export const afterEach: Function = _global.afterEach;
|
||||
export const expect: (actual: any) => jasmine.Matchers = _global.expect;
|
||||
export var afterEach: Function = _global.afterEach;
|
||||
export var expect: (actual: any) => jasmine.Matchers = _global.expect;
|
||||
|
||||
const jsmBeforeEach = _global.beforeEach;
|
||||
const jsmDescribe = _global.describe;
|
||||
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @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 {$, ExpectedConditions, browser, by, element} from 'protractor';
|
||||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
const EC = ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
}
|
||||
|
||||
describe('ngIf', () => {
|
||||
const URL = 'common/ngIf/ts/';
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
describe('ng-if-simple', () => {
|
||||
let comp = 'ng-if-simple';
|
||||
it('should hide/show content', () => {
|
||||
browser.get(URL);
|
||||
waitForElement(comp);
|
||||
expect(element.all(by.css(comp)).get(0).getText()).toEqual('hide show = true\nText to show');
|
||||
element(by.css(comp + ' button')).click();
|
||||
expect(element.all(by.css(comp)).get(0).getText()).toEqual('show show = false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ng-if-else', () => {
|
||||
let comp = 'ng-if-else';
|
||||
it('should hide/show content', () => {
|
||||
browser.get(URL);
|
||||
waitForElement(comp);
|
||||
expect(element.all(by.css(comp)).get(0).getText()).toEqual('hide show = true\nText to show');
|
||||
element(by.css(comp + ' button')).click();
|
||||
expect(element.all(by.css(comp)).get(0).getText())
|
||||
.toEqual('show show = false\nAlternate text while primary text is hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ng-if-then-else', () => {
|
||||
let comp = 'ng-if-then-else';
|
||||
it('should hide/show content', () => {
|
||||
browser.get(URL);
|
||||
waitForElement(comp);
|
||||
expect(element.all(by.css(comp)).get(0).getText())
|
||||
.toEqual('hide Switch Primary show = true\nPrimary text to show');
|
||||
element.all(by.css(comp + ' button')).get(1).click();
|
||||
expect(element.all(by.css(comp)).get(0).getText())
|
||||
.toEqual('hide Switch Primary show = true\nSecondary text to show');
|
||||
element.all(by.css(comp + ' button')).get(0).click();
|
||||
expect(element.all(by.css(comp)).get(0).getText())
|
||||
.toEqual('show Switch Primary show = false\nAlternate text while primary text is hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ng-if-let', () => {
|
||||
let comp = 'ng-if-let';
|
||||
it('should hide/show content', () => {
|
||||
browser.get(URL);
|
||||
waitForElement(comp);
|
||||
expect(element.all(by.css(comp)).get(0).getText())
|
||||
.toEqual('Next User\nWaiting... (user is null)');
|
||||
element(by.css(comp + ' button')).click();
|
||||
expect(element.all(by.css(comp)).get(0).getText()).toEqual('Next User\nHello Smith, John!');
|
||||
});
|
||||
});
|
||||
});
|
128
modules/@angular/examples/common/ngIf/ts/module.ts
Normal file
128
modules/@angular/examples/common/ngIf/ts/module.ts
Normal file
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @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 {Component, NgModule, OnInit, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
|
||||
// #docregion NgIfSimple
|
||||
@Component({
|
||||
selector: 'ng-if-simple',
|
||||
template: `
|
||||
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
|
||||
show = {{show}}
|
||||
<br>
|
||||
<div *ngIf="show">Text to show</div>
|
||||
`
|
||||
})
|
||||
class NgIfSimple {
|
||||
show: boolean = true;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion NgIfElse
|
||||
@Component({
|
||||
selector: 'ng-if-else',
|
||||
template: `
|
||||
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
|
||||
show = {{show}}
|
||||
<br>
|
||||
<div *ngIf="show; else elseBlock">Text to show</div>
|
||||
<template #elseBlock>Alternate text while primary text is hidden</template>
|
||||
`
|
||||
})
|
||||
class NgIfElse {
|
||||
show: boolean = true;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion NgIfThenElse
|
||||
@Component({
|
||||
selector: 'ng-if-then-else',
|
||||
template: `
|
||||
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
|
||||
<button (click)="switchPrimary()">Switch Primary</button>
|
||||
show = {{show}}
|
||||
<br>
|
||||
<div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div>
|
||||
<template #primaryBlock>Primary text to show</template>
|
||||
<template #secondaryBlock>Secondary text to show</template>
|
||||
<template #elseBlock>Alternate text while primary text is hidden</template>
|
||||
`
|
||||
})
|
||||
class NgIfThenElse implements OnInit {
|
||||
thenBlock: TemplateRef<any> = null;
|
||||
show: boolean = true;
|
||||
|
||||
@ViewChild('primaryBlock')
|
||||
primaryBlock: TemplateRef<any> = null;
|
||||
@ViewChild('secondaryBlock')
|
||||
secondaryBlock: TemplateRef<any> = null;
|
||||
|
||||
switchPrimary() {
|
||||
this.thenBlock = this.thenBlock === this.primaryBlock ? this.secondaryBlock : this.primaryBlock;
|
||||
}
|
||||
|
||||
ngOnInit() { this.thenBlock = this.primaryBlock; }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion NgIfLet
|
||||
@Component({
|
||||
selector: 'ng-if-let',
|
||||
template: `
|
||||
<button (click)="nextUser()">Next User</button>
|
||||
<br>
|
||||
<div *ngIf="userObservable | async; else loading; let user">
|
||||
Hello {{user.last}}, {{user.first}}!
|
||||
</div>
|
||||
<template #loading let-user>Waiting... (user is {{user|json}})</template>
|
||||
`
|
||||
})
|
||||
class NgIfLet {
|
||||
userObservable = new Subject<{first: string, last: string}>();
|
||||
first = ['John', 'Mike', 'Mary', 'Bob'];
|
||||
firstIndex = 0;
|
||||
last = ['Smith', 'Novotny', 'Angular'];
|
||||
lastIndex = 0;
|
||||
|
||||
nextUser() {
|
||||
let first = this.first[this.firstIndex++];
|
||||
if (this.firstIndex >= this.first.length) this.firstIndex = 0;
|
||||
let last = this.last[this.lastIndex++];
|
||||
if (this.lastIndex >= this.last.length) this.lastIndex = 0;
|
||||
this.userObservable.next({first, last});
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'example-app',
|
||||
template: `
|
||||
<ng-if-simple></ng-if-simple>
|
||||
<hr>
|
||||
<ng-if-else></ng-if-else>
|
||||
<hr>
|
||||
<ng-if-then-else></ng-if-then-else>
|
||||
<hr>
|
||||
<ng-if-let></ng-if-let>
|
||||
<hr>
|
||||
`
|
||||
})
|
||||
class ExampleApp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
declarations: [ExampleApp, NgIfSimple, NgIfElse, NgIfThenElse, NgIfLet],
|
||||
bootstrap: [ExampleApp]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
@ -14,6 +14,7 @@ import {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_sta
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NgModelGroup} from './directives/ng_model_group';
|
||||
import {NgNovalidate} from './directives/ng_novalidate_directive';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {RangeValueAccessor} from './directives/range_value_accessor';
|
||||
@ -44,6 +45,7 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
|
||||
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
|
||||
export const SHARED_FORM_DIRECTIVES: Type<any>[] = [
|
||||
NgNovalidate,
|
||||
NgSelectOption,
|
||||
NgSelectMultipleOption,
|
||||
DefaultValueAccessor,
|
||||
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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 {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm])',
|
||||
host: {'novalidate': ''},
|
||||
})
|
||||
export class NgNovalidate {
|
||||
}
|
@ -14,9 +14,8 @@ import {isPresent} from './facade/lang';
|
||||
import {AbstractControl} from './model';
|
||||
import {isPromise} from './private_import_core';
|
||||
|
||||
function isEmptyInputValue(value: any): boolean {
|
||||
// we don't check for string here so it also works with arrays
|
||||
return value == null || value.length === 0;
|
||||
function isEmptyInputValue(value: any) {
|
||||
return value == null || typeof value === 'string' && value.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, EventEmitter, Input, Output, forwardRef} from '@angular/core';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {AbstractControl, ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, Validator, Validators} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
@ -16,38 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
||||
export function main() {
|
||||
describe('reactive forms integration tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
declarations: [
|
||||
FormControlComp,
|
||||
FormGroupComp,
|
||||
FormArrayComp,
|
||||
FormArrayNestedGroup,
|
||||
FormControlNameSelect,
|
||||
FormControlNumberInput,
|
||||
FormControlRangeInput,
|
||||
FormControlRadioButtons,
|
||||
WrappedValue,
|
||||
WrappedValueForm,
|
||||
MyInput,
|
||||
MyInputForm,
|
||||
FormGroupNgModel,
|
||||
FormControlNgModel,
|
||||
LoginIsEmptyValidator,
|
||||
LoginIsEmptyWrapper,
|
||||
ValidationBindingsForm,
|
||||
UniqLoginValidator,
|
||||
UniqLoginWrapper,
|
||||
NestedFormGroupComp,
|
||||
FormControlCheckboxRequiredValidator,
|
||||
]
|
||||
});
|
||||
});
|
||||
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]});
|
||||
return TestBed.createComponent(component);
|
||||
}
|
||||
|
||||
describe('basic functionality', () => {
|
||||
it('should work with single controls', () => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('old value');
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -64,7 +41,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should work with formGroups (model -> view)', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -72,8 +49,17 @@ export function main() {
|
||||
expect(input.nativeElement.value).toEqual('loginValue');
|
||||
});
|
||||
|
||||
it('should add novalidate by default to form', () => {
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||
fixture.detectChanges();
|
||||
|
||||
const form = fixture.debugElement.query(By.css('form'));
|
||||
expect(form.nativeElement.getAttribute('novalidate')).toEqual('');
|
||||
});
|
||||
|
||||
it('work with formGroups (view -> model)', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('oldValue')});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -90,7 +76,7 @@ export function main() {
|
||||
describe('rebound form groups', () => {
|
||||
|
||||
it('should update DOM elements initially', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -102,7 +88,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should update model when UI changes', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -123,7 +109,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should work with radio buttons when reusing control', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const food = new FormControl('chicken');
|
||||
fixture.componentInstance.form =
|
||||
new FormGroup({'food': food, 'drink': new FormControl('')});
|
||||
@ -141,7 +127,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should update nested form group model when UI changes', () => {
|
||||
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||
const fixture = initTest(NestedFormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup(
|
||||
{'signin': new FormGroup({'login': new FormControl(), 'password': new FormControl()})});
|
||||
fixture.detectChanges();
|
||||
@ -169,7 +155,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should pick up dir validators from form controls', () => {
|
||||
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
|
||||
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
@ -193,7 +179,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should pick up dir validators from nested form groups', () => {
|
||||
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
|
||||
const form = new FormGroup({
|
||||
'signin':
|
||||
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
|
||||
@ -213,7 +199,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should strip named controls that are not found', () => {
|
||||
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
|
||||
const form = new FormGroup({
|
||||
'signin':
|
||||
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
|
||||
@ -239,7 +225,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should strip array controls that are not found', () => {
|
||||
const fixture = TestBed.createComponent(FormArrayComp);
|
||||
const fixture = initTest(FormArrayComp);
|
||||
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
|
||||
const form = new FormGroup({cities: cityArray});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -268,7 +254,7 @@ export function main() {
|
||||
|
||||
it('should attach dir to control when leaf control changes', () => {
|
||||
const form = new FormGroup({'login': new FormControl('oldValue')});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -291,7 +277,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should attach dirs to all child controls when group control changes', () => {
|
||||
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
|
||||
const form = new FormGroup({
|
||||
signin: new FormGroup(
|
||||
{login: new FormControl('oldLogin'), password: new FormControl('oldPassword')})
|
||||
@ -323,7 +309,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should attach dirs to all present child controls when array control changes', () => {
|
||||
const fixture = TestBed.createComponent(FormArrayComp);
|
||||
const fixture = initTest(FormArrayComp);
|
||||
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
|
||||
const form = new FormGroup({cities: cityArray});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -354,7 +340,7 @@ export function main() {
|
||||
|
||||
describe('form arrays', () => {
|
||||
it('should support form arrays', () => {
|
||||
const fixture = TestBed.createComponent(FormArrayComp);
|
||||
const fixture = initTest(FormArrayComp);
|
||||
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
|
||||
const form = new FormGroup({cities: cityArray});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -377,7 +363,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support pushing new controls to form arrays', () => {
|
||||
const fixture = TestBed.createComponent(FormArrayComp);
|
||||
const fixture = initTest(FormArrayComp);
|
||||
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
|
||||
const form = new FormGroup({cities: cityArray});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -393,7 +379,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support form groups nested in form arrays', () => {
|
||||
const fixture = TestBed.createComponent(FormArrayNestedGroup);
|
||||
const fixture = initTest(FormArrayNestedGroup);
|
||||
const cityArray = new FormArray([
|
||||
new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}),
|
||||
new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')})
|
||||
@ -425,7 +411,7 @@ export function main() {
|
||||
|
||||
describe('programmatic changes', () => {
|
||||
it('should update the value in the DOM when setValue() is called', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('oldValue');
|
||||
const form = new FormGroup({'login': login});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -441,7 +427,7 @@ export function main() {
|
||||
describe('disabled controls', () => {
|
||||
it('should add disabled attribute to an individual control when instantiated as disabled',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl({value: 'some value', disabled: true});
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -455,7 +441,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should add disabled attribute to formControlName when instantiated as disabled', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const control = new FormControl({value: 'some value', disabled: true});
|
||||
fixture.componentInstance.form = new FormGroup({login: control});
|
||||
fixture.componentInstance.control = control;
|
||||
@ -471,7 +457,7 @@ export function main() {
|
||||
|
||||
it('should add disabled attribute to an individual control when disable() is called',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('some value');
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -489,7 +475,7 @@ export function main() {
|
||||
|
||||
it('should add disabled attribute to child controls when disable() is called on group',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('login')});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -507,7 +493,7 @@ export function main() {
|
||||
|
||||
|
||||
it('should not add disabled attribute to custom controls when disable() is called', () => {
|
||||
const fixture = TestBed.createComponent(MyInputForm);
|
||||
const fixture = initTest(MyInputForm, MyInput);
|
||||
const control = new FormControl('some value');
|
||||
fixture.componentInstance.form = new FormGroup({login: control});
|
||||
fixture.detectChanges();
|
||||
@ -526,7 +512,7 @@ export function main() {
|
||||
describe('user input', () => {
|
||||
|
||||
it('should mark controls as touched after interacting with the DOM control', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('oldValue');
|
||||
const form = new FormGroup({'login': login});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -544,7 +530,7 @@ export function main() {
|
||||
|
||||
describe('submit and reset events', () => {
|
||||
it('should emit ngSubmit event with the original submit event on submit', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||
fixture.componentInstance.event = null;
|
||||
fixture.detectChanges();
|
||||
@ -557,7 +543,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should mark formGroup as submitted on submit event', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -572,7 +558,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should set value in UI when form resets to that value programmatically', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('some value');
|
||||
const form = new FormGroup({'login': login});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -586,7 +572,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should clear value in UI when form resets programmatically', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('some value');
|
||||
const form = new FormGroup({'login': login});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -604,7 +590,7 @@ export function main() {
|
||||
describe('value changes and status changes', () => {
|
||||
|
||||
it('should mark controls as dirty before emitting a value change event', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('oldValue');
|
||||
fixture.componentInstance.form = new FormGroup({'login': login});
|
||||
fixture.detectChanges();
|
||||
@ -619,7 +605,7 @@ export function main() {
|
||||
|
||||
it('should mark control as pristine before emitting a value change event when resetting ',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const login = new FormControl('oldValue');
|
||||
const form = new FormGroup({'login': login});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -640,7 +626,7 @@ export function main() {
|
||||
|
||||
describe('setting status classes', () => {
|
||||
it('should work with single fields', () => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('', Validators.required);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -661,7 +647,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should work with single fields and async validators', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('', null, uniqLoginAsyncValidator('good'));
|
||||
fixture.debugElement.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -682,7 +668,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should work with single fields that combines async and sync validators', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control =
|
||||
new FormControl('', Validators.required, uniqLoginAsyncValidator('good'));
|
||||
fixture.debugElement.componentInstance.control = control;
|
||||
@ -715,7 +701,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should work with single fields in parent forms', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('', Validators.required)});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -736,7 +722,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should work with formGroup', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('', Validators.required)});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -765,7 +751,7 @@ export function main() {
|
||||
it('should support <input> without type', () => {
|
||||
TestBed.overrideComponent(
|
||||
FormControlComp, {set: {template: `<input [formControl]="control">`}});
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('old');
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -782,7 +768,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support <input type=text>', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('old')});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -799,7 +785,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should ignore the change event for <input type=text>', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('oldValue')});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -814,7 +800,7 @@ export function main() {
|
||||
it('should support <textarea>', () => {
|
||||
TestBed.overrideComponent(
|
||||
FormControlComp, {set: {template: `<textarea [formControl]="control"></textarea>`}});
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl('old');
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -833,7 +819,7 @@ export function main() {
|
||||
it('should support <type=checkbox>', () => {
|
||||
TestBed.overrideComponent(
|
||||
FormControlComp, {set: {template: `<input type="checkbox" [formControl]="control">`}});
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control = new FormControl(true);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -850,7 +836,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support <select>', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNameSelect);
|
||||
const fixture = initTest(FormControlNameSelect);
|
||||
fixture.detectChanges();
|
||||
|
||||
// model -> view
|
||||
@ -870,7 +856,7 @@ export function main() {
|
||||
|
||||
describe('should support <type=number>', () => {
|
||||
it('with basic use case', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNumberInput);
|
||||
const fixture = initTest(FormControlNumberInput);
|
||||
const control = new FormControl(10);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -887,7 +873,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('when value is cleared in the UI', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNumberInput);
|
||||
const fixture = initTest(FormControlNumberInput);
|
||||
const control = new FormControl(10, Validators.required);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -907,7 +893,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('when value is cleared programmatically', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNumberInput);
|
||||
const fixture = initTest(FormControlNumberInput);
|
||||
const control = new FormControl(10);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -922,7 +908,7 @@ export function main() {
|
||||
describe('should support <type=radio>', () => {
|
||||
|
||||
it('should support basic functionality', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form =
|
||||
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -949,7 +935,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support an initial undefined value', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form = new FormGroup({'food': new FormControl(), 'drink': new FormControl()});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -960,7 +946,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should reset properly', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form =
|
||||
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -975,7 +961,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should set value to null and undefined properly', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form = new FormGroup(
|
||||
{'food': new FormControl('chicken'), 'drink': new FormControl('sprite')});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -996,7 +982,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use formControlName to group radio buttons when name is absent', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const foodCtrl = new FormControl('fish');
|
||||
const drinkCtrl = new FormControl('sprite');
|
||||
fixture.componentInstance.form = new FormGroup({'food': foodCtrl, 'drink': drinkCtrl});
|
||||
@ -1028,7 +1014,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support removing controls from <type=radio>', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const showRadio = new FormControl('yes');
|
||||
const form =
|
||||
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
|
||||
@ -1062,7 +1048,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form = new FormGroup({
|
||||
food: new FormControl('fish'),
|
||||
nested: new FormGroup({food: new FormControl('fish')})
|
||||
@ -1091,7 +1077,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should disable all radio buttons when disable() is called', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form =
|
||||
new FormGroup({food: new FormControl('fish'), drink: new FormControl('cola')});
|
||||
fixture.componentInstance.form = form;
|
||||
@ -1123,7 +1109,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should disable all radio buttons when initially disabled', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRadioButtons);
|
||||
const fixture = initTest(FormControlRadioButtons);
|
||||
const form = new FormGroup({
|
||||
food: new FormControl({value: 'fish', disabled: true}),
|
||||
drink: new FormControl('cola')
|
||||
@ -1142,7 +1128,7 @@ export function main() {
|
||||
|
||||
describe('should support <type=range>', () => {
|
||||
it('with basic use case', () => {
|
||||
const fixture = TestBed.createComponent(FormControlRangeInput);
|
||||
const fixture = initTest(FormControlRangeInput);
|
||||
const control = new FormControl(10);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -1159,7 +1145,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('when value is cleared in the UI', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNumberInput);
|
||||
const fixture = initTest(FormControlNumberInput);
|
||||
const control = new FormControl(10, Validators.required);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -1179,7 +1165,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('when value is cleared programmatically', () => {
|
||||
const fixture = TestBed.createComponent(FormControlNumberInput);
|
||||
const fixture = initTest(FormControlNumberInput);
|
||||
const control = new FormControl(10);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -1193,7 +1179,7 @@ export function main() {
|
||||
|
||||
describe('custom value accessors', () => {
|
||||
it('should support basic functionality', () => {
|
||||
const fixture = TestBed.createComponent(WrappedValueForm);
|
||||
const fixture = initTest(WrappedValueForm, WrappedValue);
|
||||
const form = new FormGroup({'login': new FormControl('aa')});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -1216,7 +1202,7 @@ export function main() {
|
||||
|
||||
it('should support non builtin input elements that fire a change event without a \'target\' property',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(MyInputForm);
|
||||
const fixture = initTest(MyInputForm, MyInput);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')});
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -1231,7 +1217,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support custom accessors without setDisabledState - formControlName', () => {
|
||||
const fixture = TestBed.createComponent(WrappedValueForm);
|
||||
const fixture = initTest(WrappedValueForm, WrappedValue);
|
||||
fixture.componentInstance.form = new FormGroup({
|
||||
'login': new FormControl({value: 'aa', disabled: true}),
|
||||
});
|
||||
@ -1245,7 +1231,7 @@ export function main() {
|
||||
TestBed.overrideComponent(
|
||||
FormControlComp,
|
||||
{set: {template: `<input type="text" [formControl]="control" wrapped-value>`}});
|
||||
const fixture = TestBed.createComponent(FormControlComp);
|
||||
const fixture = initTest(FormControlComp);
|
||||
fixture.componentInstance.control = new FormControl({value: 'aa', disabled: true});
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.control.status).toEqual('DISABLED');
|
||||
@ -1258,7 +1244,7 @@ export function main() {
|
||||
describe('ngModel interactions', () => {
|
||||
|
||||
it('should support ngModel for complex forms', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormGroupNgModel);
|
||||
const fixture = initTest(FormGroupNgModel);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('')});
|
||||
fixture.componentInstance.login = 'oldValue';
|
||||
fixture.detectChanges();
|
||||
@ -1275,7 +1261,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support ngModel for single fields', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlNgModel);
|
||||
const fixture = initTest(FormControlNgModel);
|
||||
fixture.componentInstance.control = new FormControl('');
|
||||
fixture.componentInstance.login = 'oldValue';
|
||||
fixture.detectChanges();
|
||||
@ -1292,7 +1278,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should not update the view when the value initially came from the view', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormControlNgModel);
|
||||
const fixture = initTest(FormControlNgModel);
|
||||
fixture.componentInstance.control = new FormControl('');
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -1313,7 +1299,7 @@ export function main() {
|
||||
|
||||
describe('validations', () => {
|
||||
it('required validator should validate checkbox', () => {
|
||||
const fixture = TestBed.createComponent(FormControlCheckboxRequiredValidator);
|
||||
const fixture = initTest(FormControlCheckboxRequiredValidator);
|
||||
const control = new FormControl(false, Validators.requiredTrue);
|
||||
fixture.componentInstance.control = control;
|
||||
fixture.detectChanges();
|
||||
@ -1331,7 +1317,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use sync validators defined in html', () => {
|
||||
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
|
||||
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
@ -1376,7 +1362,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use sync validators using bindings', () => {
|
||||
const fixture = TestBed.createComponent(ValidationBindingsForm);
|
||||
const fixture = initTest(ValidationBindingsForm);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
@ -1424,7 +1410,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('changes on bound properties should change the validation state of the form', () => {
|
||||
const fixture = TestBed.createComponent(ValidationBindingsForm);
|
||||
const fixture = initTest(ValidationBindingsForm);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
@ -1499,7 +1485,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support rebound controls with rebound validators', () => {
|
||||
const fixture = TestBed.createComponent(ValidationBindingsForm);
|
||||
const fixture = initTest(ValidationBindingsForm);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
@ -1536,7 +1522,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use async validators defined in the html', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(UniqLoginWrapper);
|
||||
const fixture = initTest(UniqLoginWrapper, UniqLoginValidator);
|
||||
const form = new FormGroup({'login': new FormControl('')});
|
||||
tick();
|
||||
fixture.componentInstance.form = form;
|
||||
@ -1556,7 +1542,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should use sync validators defined in the model', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const form = new FormGroup({'login': new FormControl('aa', Validators.required)});
|
||||
fixture.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
@ -1570,7 +1556,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should use async validators defined in the model', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const control =
|
||||
new FormControl('', Validators.required, uniqLoginAsyncValidator('expected'));
|
||||
const form = new FormGroup({'login': control});
|
||||
@ -1601,7 +1587,7 @@ export function main() {
|
||||
describe('errors', () => {
|
||||
|
||||
it('should throw if a form isn\'t passed into formGroup', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(new RegExp(`formGroup expects a FormGroup instance`));
|
||||
@ -1615,7 +1601,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1632,7 +1618,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1651,7 +1637,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1668,7 +1654,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1687,7 +1673,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1703,7 +1689,7 @@ export function main() {
|
||||
</div>`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
@ -1720,7 +1706,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.myGroup = new FormGroup({});
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
@ -1738,7 +1724,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.myGroup = new FormGroup({});
|
||||
|
||||
expect(() => fixture.detectChanges()).not.toThrowError();
|
||||
@ -1756,7 +1742,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
const myGroup = new FormGroup({person: new FormGroup({})});
|
||||
fixture.componentInstance.myGroup = new FormGroup({person: new FormGroup({})});
|
||||
|
||||
@ -1777,7 +1763,7 @@ export function main() {
|
||||
`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.myGroup = new FormGroup({});
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
@ -1794,7 +1780,7 @@ export function main() {
|
||||
</form>`
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
const fixture = initTest(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'food': new FormControl('fish')});
|
||||
|
||||
expect(() => fixture.detectChanges())
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, Input, forwardRef} from '@angular/core';
|
||||
import {Component, Directive, Input, Type, forwardRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {AbstractControl, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, Validator} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
@ -16,37 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
||||
export function main() {
|
||||
describe('template-driven forms integration tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
StandaloneNgModel,
|
||||
NgModelForm,
|
||||
NgModelGroupForm,
|
||||
NgModelValidBinding,
|
||||
NgModelNgIfForm,
|
||||
NgModelRadioForm,
|
||||
NgModelRangeForm,
|
||||
NgModelSelectForm,
|
||||
NgNoFormComp,
|
||||
InvalidNgModelNoName,
|
||||
NgModelOptionsStandalone,
|
||||
NgModelCustomComp,
|
||||
NgModelCustomWrapper,
|
||||
NgModelValidationBindings,
|
||||
NgModelMultipleValidators,
|
||||
NgAsyncValidator,
|
||||
NgModelAsyncValidation,
|
||||
NgModelSelectMultipleForm,
|
||||
NgModelSelectWithNullForm,
|
||||
NgModelCheckboxRequiredValidator,
|
||||
],
|
||||
imports: [FormsModule]
|
||||
});
|
||||
});
|
||||
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [component, ...directives], imports: [FormsModule]});
|
||||
return TestBed.createComponent(component);
|
||||
}
|
||||
|
||||
describe('basic functionality', () => {
|
||||
it('should support ngModel for standalone fields', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(StandaloneNgModel);
|
||||
const fixture = initTest(StandaloneNgModel);
|
||||
fixture.componentInstance.name = 'oldValue';
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -65,7 +43,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support ngModel registration with a parent form', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.name = 'Nancy';
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -76,8 +54,18 @@ export function main() {
|
||||
expect(form.valid).toBe(false);
|
||||
}));
|
||||
|
||||
it('should add novalidate by default to form element', fakeAsync(() => {
|
||||
const fixture = initTest(NgModelForm);
|
||||
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const form = fixture.debugElement.query(By.css('form'));
|
||||
expect(form.nativeElement.getAttribute('novalidate')).toEqual('');
|
||||
}));
|
||||
|
||||
it('should support ngModelGroup', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.first = 'Nancy';
|
||||
fixture.componentInstance.last = 'Drew';
|
||||
fixture.componentInstance.email = 'some email';
|
||||
@ -100,7 +88,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should add controls and control groups to form control model', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.first = 'Nancy';
|
||||
fixture.componentInstance.last = 'Drew';
|
||||
fixture.componentInstance.email = 'some email';
|
||||
@ -115,7 +103,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should remove controls and control groups from form control model', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelNgIfForm);
|
||||
const fixture = initTest(NgModelNgIfForm);
|
||||
fixture.componentInstance.emailShowing = true;
|
||||
fixture.componentInstance.first = 'Nancy';
|
||||
fixture.componentInstance.email = 'some email';
|
||||
@ -148,7 +136,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should set status classes with ngModel', async(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.name = 'aa';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
@ -171,7 +159,7 @@ export function main() {
|
||||
|
||||
it('should set status classes with ngModel and async validators', fakeAsync(() => {
|
||||
|
||||
const fixture = TestBed.createComponent(NgModelAsyncValidation);
|
||||
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -193,7 +181,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should set status classes with ngModelGroup and ngForm', async(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.first = '';
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -228,27 +216,34 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should not create a template-driven form when ngNoForm is used', () => {
|
||||
const fixture = TestBed.createComponent(NgNoFormComp);
|
||||
const fixture = initTest(NgNoFormComp);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not add novalidate when ngNoForm is used', () => {
|
||||
const fixture = initTest(NgNoFormComp);
|
||||
fixture.detectChanges();
|
||||
const form = fixture.debugElement.query(By.css('form'));
|
||||
expect(form.nativeElement.hasAttribute('novalidate')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('name and ngModelOptions', () => {
|
||||
it('should throw if ngModel has a parent form but no name attr or standalone label', () => {
|
||||
const fixture = TestBed.createComponent(InvalidNgModelNoName);
|
||||
const fixture = initTest(InvalidNgModelNoName);
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(new RegExp(`name attribute must be set`));
|
||||
});
|
||||
|
||||
it('should not throw if ngModel has a parent form, no name attr, and a standalone label',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
|
||||
const fixture = initTest(NgModelOptionsStandalone);
|
||||
expect(() => fixture.detectChanges()).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not register standalone ngModels with parent form', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
|
||||
const fixture = initTest(NgModelOptionsStandalone);
|
||||
fixture.componentInstance.one = 'some data';
|
||||
fixture.componentInstance.two = 'should not show';
|
||||
fixture.detectChanges();
|
||||
@ -263,7 +258,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should override name attribute with ngModelOptions name if provided', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.options = {name: 'override'};
|
||||
fixture.componentInstance.name = 'some data';
|
||||
fixture.detectChanges();
|
||||
@ -276,7 +271,7 @@ export function main() {
|
||||
|
||||
describe('submit and reset events', () => {
|
||||
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.event = null;
|
||||
|
||||
const form = fixture.debugElement.query(By.css('form'));
|
||||
@ -287,7 +282,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should mark NgForm as submitted on submit event', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
|
||||
tick();
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
@ -301,7 +296,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should reset the form to empty when reset event is fired', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.name = 'should be cleared';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -324,7 +319,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should reset the form submit state when reset button is clicked', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
const formEl = fixture.debugElement.query(By.css('form'));
|
||||
|
||||
@ -342,7 +337,7 @@ export function main() {
|
||||
|
||||
describe('valueChange and statusChange events', () => {
|
||||
it('should emit valueChanges and statusChanges on init', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
fixture.componentInstance.name = 'aa';
|
||||
fixture.detectChanges();
|
||||
@ -363,7 +358,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should mark controls dirty before emitting the value change event', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -380,7 +375,7 @@ export function main() {
|
||||
|
||||
it('should mark controls pristine before emitting the value change event when resetting ',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
@ -402,7 +397,7 @@ export function main() {
|
||||
|
||||
describe('disabled controls', () => {
|
||||
it('should not consider disabled controls in value or validation', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.isDisabled = false;
|
||||
fixture.componentInstance.first = '';
|
||||
fixture.componentInstance.last = 'Drew';
|
||||
@ -426,7 +421,7 @@ export function main() {
|
||||
|
||||
it('should add disabled attribute in the UI if disable() is called programmatically',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelGroupForm);
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.isDisabled = false;
|
||||
fixture.componentInstance.first = 'Nancy';
|
||||
fixture.detectChanges();
|
||||
@ -442,7 +437,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should disable a custom control if disabled attr is added', async(() => {
|
||||
const fixture = TestBed.createComponent(NgModelCustomWrapper);
|
||||
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
|
||||
fixture.componentInstance.name = 'Nancy';
|
||||
fixture.componentInstance.isDisabled = true;
|
||||
fixture.detectChanges();
|
||||
@ -468,7 +463,7 @@ export function main() {
|
||||
`,
|
||||
}
|
||||
});
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
@ -484,7 +479,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should disable radio controls properly with programmatic call', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.componentInstance.food = 'fish';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -520,7 +515,7 @@ export function main() {
|
||||
|
||||
describe('range control', () => {
|
||||
it('should support <type=range>', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRangeForm);
|
||||
const fixture = initTest(NgModelRangeForm);
|
||||
// model -> view
|
||||
fixture.componentInstance.val = 4;
|
||||
fixture.detectChanges();
|
||||
@ -540,7 +535,7 @@ export function main() {
|
||||
|
||||
describe('radio controls', () => {
|
||||
it('should support <type=radio>', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.componentInstance.food = 'fish';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -559,7 +554,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support multiple named <type=radio> groups', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.componentInstance.food = 'fish';
|
||||
fixture.componentInstance.drink = 'sprite';
|
||||
fixture.detectChanges();
|
||||
@ -582,7 +577,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support initial undefined value', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
@ -594,7 +589,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support resetting properly', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.componentInstance.food = 'chicken';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -610,7 +605,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support setting value to null and undefined', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelRadioForm);
|
||||
const fixture = initTest(NgModelRadioForm);
|
||||
fixture.componentInstance.food = 'chicken';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -638,7 +633,7 @@ export function main() {
|
||||
|
||||
describe('select controls', () => {
|
||||
it('with option values that are objects', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectForm);
|
||||
const fixture = initTest(NgModelSelectForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
||||
comp.selectedCity = comp.cities[1];
|
||||
@ -662,7 +657,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('when new options are added', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectForm);
|
||||
const fixture = initTest(NgModelSelectForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
||||
comp.selectedCity = comp.cities[1];
|
||||
@ -681,7 +676,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('when options are removed', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectForm);
|
||||
const fixture = initTest(NgModelSelectForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
||||
comp.selectedCity = comp.cities[1];
|
||||
@ -699,7 +694,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('when option values have same content, but different identities', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectForm);
|
||||
const fixture = initTest(NgModelSelectForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}];
|
||||
comp.selectedCity = comp.cities[0];
|
||||
@ -716,7 +711,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should work with null option', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
|
||||
const fixture = initTest(NgModelSelectWithNullForm);
|
||||
const comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
|
||||
comp.selectedCity = null;
|
||||
@ -743,7 +738,7 @@ export function main() {
|
||||
let comp: NgModelSelectMultipleForm;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NgModelSelectMultipleForm);
|
||||
fixture = initTest(NgModelSelectMultipleForm);
|
||||
comp = fixture.componentInstance;
|
||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
||||
});
|
||||
@ -804,7 +799,7 @@ export function main() {
|
||||
|
||||
describe('custom value accessors', () => {
|
||||
it('should support standard writing to view and model', async(() => {
|
||||
const fixture = TestBed.createComponent(NgModelCustomWrapper);
|
||||
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
|
||||
fixture.componentInstance.name = 'Nancy';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
@ -829,7 +824,7 @@ export function main() {
|
||||
describe('validation directives', () => {
|
||||
|
||||
it('required validator should validate checkbox', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelCheckboxRequiredValidator);
|
||||
const fixture = initTest(NgModelCheckboxRequiredValidator);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
@ -865,7 +860,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support dir validators using bindings', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
||||
const fixture = initTest(NgModelValidationBindings);
|
||||
fixture.componentInstance.required = true;
|
||||
fixture.componentInstance.minLen = 3;
|
||||
fixture.componentInstance.maxLen = 3;
|
||||
@ -909,7 +904,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support optional fields with string pattern validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
const fixture = initTest(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.pattern = '[a-z]+';
|
||||
fixture.detectChanges();
|
||||
@ -931,7 +926,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
const fixture = initTest(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.pattern = /^[a-z]+$/;
|
||||
fixture.detectChanges();
|
||||
@ -953,7 +948,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should support optional fields with minlength validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
const fixture = initTest(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.minLen = 2;
|
||||
fixture.detectChanges();
|
||||
@ -976,7 +971,7 @@ export function main() {
|
||||
|
||||
it('changes on bound properties should change the validation state of the form',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
||||
const fixture = initTest(NgModelValidationBindings);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
@ -1050,7 +1045,7 @@ export function main() {
|
||||
describe('ngModel corner cases', () => {
|
||||
it('should update the view when the model is set back to what used to be in the view',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(StandaloneNgModel);
|
||||
const fixture = initTest(StandaloneNgModel);
|
||||
fixture.componentInstance.name = '';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
@ -1078,7 +1073,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should not crash when validity is checked from a binding', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidBinding);
|
||||
const fixture = initTest(NgModelValidBinding);
|
||||
tick();
|
||||
expect(() => fixture.detectChanges()).not.toThrowError();
|
||||
}));
|
||||
|
@ -48,12 +48,6 @@ export function main() {
|
||||
|
||||
it('should accept zero as valid',
|
||||
() => { expect(Validators.required(new FormControl(0))).toBeNull(); });
|
||||
|
||||
it('should error on an empty array',
|
||||
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
|
||||
|
||||
it('should not error on a non-empty array',
|
||||
() => expect(Validators.required(new FormControl([1, 2]))).toBeNull());
|
||||
});
|
||||
|
||||
describe('requiredTrue', () => {
|
||||
|
@ -62,7 +62,15 @@ export class RequestOptions {
|
||||
/**
|
||||
* Search parameters to be included in a {@link Request}.
|
||||
*/
|
||||
search: URLSearchParams;
|
||||
params: URLSearchParams;
|
||||
/**
|
||||
* @deprecated from 4.0.0. Use params instead.
|
||||
*/
|
||||
get search(): URLSearchParams { return this.params; }
|
||||
/**
|
||||
* @deprecated from 4.0.0. Use params instead.
|
||||
*/
|
||||
set search(params: URLSearchParams) { this.params = params; }
|
||||
/**
|
||||
* Enable use credentials for a {@link Request}.
|
||||
*/
|
||||
@ -72,15 +80,15 @@ export class RequestOptions {
|
||||
*/
|
||||
responseType: ResponseContentType;
|
||||
|
||||
// TODO(Dzmitry): remove search when this.search is removed
|
||||
constructor(
|
||||
{method, headers, body, url, search, withCredentials,
|
||||
{method, headers, body, url, search, params, withCredentials,
|
||||
responseType}: RequestOptionsArgs = {}) {
|
||||
this.method = method != null ? normalizeMethodName(method) : null;
|
||||
this.headers = headers != null ? headers : null;
|
||||
this.body = body != null ? body : null;
|
||||
this.url = url != null ? url : null;
|
||||
this.search =
|
||||
search != null ? (typeof search === 'string' ? new URLSearchParams(search) : search) : null;
|
||||
this.params = this._mergeSearchParams(params || search);
|
||||
this.withCredentials = withCredentials != null ? withCredentials : null;
|
||||
this.responseType = responseType != null ? responseType : null;
|
||||
}
|
||||
@ -116,18 +124,49 @@ export class RequestOptions {
|
||||
headers: options && options.headers != null ? options.headers : new Headers(this.headers),
|
||||
body: options && options.body != null ? options.body : this.body,
|
||||
url: options && options.url != null ? options.url : this.url,
|
||||
search: options && options.search != null ?
|
||||
(typeof options.search === 'string' ? new URLSearchParams(options.search) :
|
||||
options.search.clone()) :
|
||||
this.search,
|
||||
params: options && this._mergeSearchParams(options.params || options.search),
|
||||
withCredentials: options && options.withCredentials != null ? options.withCredentials :
|
||||
this.withCredentials,
|
||||
responseType: options && options.responseType != null ? options.responseType :
|
||||
this.responseType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _mergeSearchParams(params: string|URLSearchParams|
|
||||
{[key: string]: any | any[]}): URLSearchParams {
|
||||
if (!params) return this.params;
|
||||
|
||||
if (params instanceof URLSearchParams) {
|
||||
return params.clone();
|
||||
}
|
||||
|
||||
if (typeof params === 'string') {
|
||||
return new URLSearchParams(params);
|
||||
}
|
||||
|
||||
return this._parseParams(params);
|
||||
}
|
||||
|
||||
private _parseParams(objParams: {[key: string]: any | any[]} = {}): URLSearchParams {
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(objParams).forEach((key: string) => {
|
||||
const value: any|any[] = objParams[key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item: any) => this._appendParam(key, item, params));
|
||||
} else {
|
||||
this._appendParam(key, value, params);
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
private _appendParam(key: string, value: any, params: URLSearchParams): void {
|
||||
if (typeof value !== 'string') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link RequestOptions}, with default values.
|
||||
|
@ -48,7 +48,9 @@ export abstract class XSRFStrategy { abstract configureRequest(req: Request): vo
|
||||
export interface RequestOptionsArgs {
|
||||
url?: string;
|
||||
method?: string|RequestMethod;
|
||||
search?: string|URLSearchParams;
|
||||
/** @deprecated from 4.0.0. Use params instead. */
|
||||
search?: string|URLSearchParams|{[key: string]: any | any[]};
|
||||
params?: string|URLSearchParams|{[key: string]: any | any[]};
|
||||
headers?: Headers;
|
||||
body?: any;
|
||||
withCredentials?: boolean;
|
||||
|
@ -76,15 +76,15 @@ export class Request extends Body {
|
||||
// TODO: assert that url is present
|
||||
const url = requestOptions.url;
|
||||
this.url = requestOptions.url;
|
||||
if (requestOptions.search) {
|
||||
const search = requestOptions.search.toString();
|
||||
if (search.length > 0) {
|
||||
if (requestOptions.params) {
|
||||
const params = requestOptions.params.toString();
|
||||
if (params.length > 0) {
|
||||
let prefix = '?';
|
||||
if (this.url.indexOf('?') != -1) {
|
||||
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
|
||||
}
|
||||
// TODO: just delete search-query-looking string in url?
|
||||
this.url = url + prefix + search;
|
||||
this.url = url + prefix + params;
|
||||
}
|
||||
}
|
||||
this._body = requestOptions.body;
|
||||
|
@ -25,5 +25,32 @@ export function main() {
|
||||
const options2 = options1.merge(new RequestOptions({method: RequestMethod.Delete}));
|
||||
expect(options2.method).toBe(RequestMethod.Delete);
|
||||
});
|
||||
|
||||
it('should accept search params as object', () => {
|
||||
const params = {a: 1, b: 'text', c: [1, 2, '3']};
|
||||
const options = new RequestOptions({params});
|
||||
|
||||
expect(options.params.paramsMap.size).toBe(3);
|
||||
expect(options.params.paramsMap.get('a')).toEqual(['1']);
|
||||
expect(options.params.paramsMap.get('b')).toEqual(['text']);
|
||||
expect(options.params.paramsMap.get('c')).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it('should merge search params as object', () => {
|
||||
const options1 = new BaseRequestOptions();
|
||||
const params = {a: 1, b: 'text', c: [1, 2, '3']};
|
||||
const options2 = options1.merge(new RequestOptions({params}));
|
||||
|
||||
expect(options2.params.paramsMap.size).toBe(3);
|
||||
expect(options2.params.paramsMap.get('a')).toEqual(['1']);
|
||||
expect(options2.params.paramsMap.get('b')).toEqual(['text']);
|
||||
expect(options2.params.paramsMap.get('c')).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it('should create a new headers object when calling merge', () => {
|
||||
const options1 = new RequestOptions({headers: new Headers()});
|
||||
const options2 = options1.merge();
|
||||
expect(options2.headers).not.toBe(options1.headers);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -114,78 +114,25 @@ export class MockConnection implements Connection {
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Injectable, ReflectiveInjector} from '@angular/core';
|
||||
* import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
|
||||
* import {Response, ResponseOptions} from '@angular/http';
|
||||
* import {MockBackend, MockConnection} from '@angular/http/testing';
|
||||
*
|
||||
* const HERO_ONE = 'HeroNrOne';
|
||||
* const HERO_TWO = 'WillBeAlwaysTheSecond';
|
||||
*
|
||||
* @Injectable()
|
||||
* class HeroService {
|
||||
* constructor(private http: Http) {}
|
||||
*
|
||||
* getHeroes(): Promise<String[]> {
|
||||
* return this.http.get('myservices.de/api/heroes')
|
||||
* .toPromise()
|
||||
* .then(response => response.json().data)
|
||||
* .catch(e => this.handleError(e));
|
||||
* }
|
||||
*
|
||||
* private handleError(error: any): Promise<any> {
|
||||
* console.error('An error occurred', error);
|
||||
* return Promise.reject(error.message || error);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* describe('MockBackend HeroService Example', () => {
|
||||
* beforeEach(() => {
|
||||
* this.injector = ReflectiveInjector.resolveAndCreate([
|
||||
* {provide: ConnectionBackend, useClass: MockBackend},
|
||||
* {provide: RequestOptions, useClass: BaseRequestOptions},
|
||||
* Http,
|
||||
* HeroService,
|
||||
* ]);
|
||||
* this.heroService = this.injector.get(HeroService);
|
||||
* this.backend = this.injector.get(ConnectionBackend) as MockBackend;
|
||||
* this.backend.connections.subscribe((connection: any) => this.lastConnection = connection);
|
||||
* import {BaseRequestOptions, Http} from '@angular/http';
|
||||
* import {MockBackend} from '@angular/http/testing';
|
||||
* it('should get some data', inject([AsyncTestCompleter], (async) => {
|
||||
* var connection;
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* {provide: Http, useFactory: (backend, options) => {
|
||||
* return new Http(backend, options);
|
||||
* }, deps: [MockBackend, BaseRequestOptions]}]);
|
||||
* var http = injector.get(Http);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* //Assign any newly-created connection to local variable
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('data.json').subscribe((res) => {
|
||||
* expect(res.text()).toBe('awesome');
|
||||
* async.done();
|
||||
* });
|
||||
*
|
||||
* it('getHeroes() should query current service url', () => {
|
||||
* this.heroService.getHeroes();
|
||||
* expect(this.lastConnection).toBeDefined('no http service connection at all?');
|
||||
* expect(this.lastConnection.request.url).toMatch(/api\/heroes$/, 'url invalid');
|
||||
* });
|
||||
*
|
||||
* it('getHeroes() should return some heroes', fakeAsync(() => {
|
||||
* let result: String[];
|
||||
* this.heroService.getHeroes().then((heroes: String[]) => result = heroes);
|
||||
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
|
||||
* body: JSON.stringify({data: [HERO_ONE, HERO_TWO]}),
|
||||
* })));
|
||||
* tick();
|
||||
* expect(result.length).toEqual(2, 'should contain given amount of heroes');
|
||||
* expect(result[0]).toEqual(HERO_ONE, ' HERO_ONE should be the first hero');
|
||||
* expect(result[1]).toEqual(HERO_TWO, ' HERO_TWO should be the second hero');
|
||||
* }));
|
||||
*
|
||||
* it('getHeroes() while server is down', fakeAsync(() => {
|
||||
* let result: String[];
|
||||
* let catchedError: any;
|
||||
* this.heroService.getHeroes()
|
||||
* .then((heroes: String[]) => result = heroes)
|
||||
* .catch((error: any) => catchedError = error);
|
||||
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
|
||||
* status: 404,
|
||||
* statusText: 'URL not Found',
|
||||
* })));
|
||||
* tick();
|
||||
* expect(result).toBeUndefined();
|
||||
* expect(catchedError).toBeDefined();
|
||||
* }));
|
||||
* });
|
||||
* connection.mockRespond(new Response('awesome'));
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
@ -202,30 +149,27 @@ export class MockBackend implements ConnectionBackend {
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {ReflectiveInjector} from '@angular/core';
|
||||
* import {fakeAsync, tick} from '@angular/core/testing';
|
||||
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
|
||||
* import {Response, ResponseOptions} from '@angular/http';
|
||||
* import {MockBackend, MockConnection} from '@angular/http/testing';
|
||||
* import {Http, BaseRequestOptions, Response} from '@angular/http';
|
||||
* import {MockBackend} from '@angular/http/testing';
|
||||
* import {Injector, provide} from '@angular/core';
|
||||
*
|
||||
* it('should get a response', fakeAsync(() => {
|
||||
* let connection:
|
||||
* MockConnection; // this will be set when a new connection is emitted from the
|
||||
* // backend.
|
||||
* let text: string; // this will be set from mock response
|
||||
* let injector = ReflectiveInjector.resolveAndCreate([
|
||||
* {provide: ConnectionBackend, useClass: MockBackend},
|
||||
* {provide: RequestOptions, useClass: BaseRequestOptions},
|
||||
* Http,
|
||||
* ]);
|
||||
* let backend = injector.get(ConnectionBackend);
|
||||
* let http = injector.get(Http);
|
||||
* backend.connections.subscribe((c: MockConnection) => connection = c);
|
||||
* http.request('something.json').toPromise().then((res: any) => text = res.text());
|
||||
* connection.mockRespond(new Response(new ResponseOptions({body: 'Something'})));
|
||||
* tick();
|
||||
* expect(text).toBe('Something');
|
||||
* }));
|
||||
* it('should get a response', () => {
|
||||
* var connection; //this will be set when a new connection is emitted from the backend.
|
||||
* var text; //this will be set from mock response
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* {provide: Http, useFactory: (backend, options) => {
|
||||
* return new Http(backend, options);
|
||||
* }, deps: [MockBackend, BaseRequestOptions]}]);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* var http = injector.get(Http);
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('something.json').subscribe(res => {
|
||||
* text = res.text();
|
||||
* });
|
||||
* connection.mockRespond(new Response({body: 'Something'}));
|
||||
* expect(text).toBe('Something');
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
|
@ -208,12 +208,13 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
|
||||
private diagnoseExpression(ast: AST, offset: number, includeEvent: boolean) {
|
||||
const scope = this.getExpressionScope(this.path, includeEvent);
|
||||
this.diagnostics.push(
|
||||
...getExpressionDiagnostics(scope, ast, this.info.template.query)
|
||||
.map(d => ({
|
||||
span: offsetSpan(d.ast.span, offset + this.info.template.span.start),
|
||||
kind: d.kind,
|
||||
message: d.message
|
||||
})));
|
||||
...getExpressionDiagnostics(scope, ast, this.info.template.query, {
|
||||
event: includeEvent
|
||||
}).map(d => ({
|
||||
span: offsetSpan(d.ast.span, offset + this.info.template.span.start),
|
||||
kind: d.kind,
|
||||
message: d.message
|
||||
})));
|
||||
}
|
||||
|
||||
private push(ast: TemplateAst) { this.path.push(ast); }
|
||||
|
@ -16,9 +16,12 @@ import {TemplateAstChildVisitor, TemplateAstPath} from './template_path';
|
||||
import {BuiltinType, CompletionKind, Definition, DiagnosticKind, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './types';
|
||||
import {inSpan, spanOf} from './utils';
|
||||
|
||||
export interface ExpressionDiagnosticsContext { event?: boolean; }
|
||||
|
||||
export function getExpressionDiagnostics(
|
||||
scope: SymbolTable, ast: AST, query: SymbolQuery): TypeDiagnostic[] {
|
||||
const analyzer = new AstType(scope, query);
|
||||
scope: SymbolTable, ast: AST, query: SymbolQuery,
|
||||
context: ExpressionDiagnosticsContext = {}): TypeDiagnostic[] {
|
||||
const analyzer = new AstType(scope, query, context);
|
||||
analyzer.getDiagnostics(ast);
|
||||
return analyzer.diagnostics;
|
||||
}
|
||||
@ -30,7 +33,7 @@ export function getExpressionCompletions(
|
||||
const tail = path.tail;
|
||||
let result: SymbolTable|undefined = scope;
|
||||
|
||||
function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); }
|
||||
function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); }
|
||||
|
||||
// If the completion request is in a not in a pipe or property access then the global scope
|
||||
// (that is the scope of the implicit receiver) is the right scope as the user is typing the
|
||||
@ -88,7 +91,7 @@ export function getExpressionSymbol(
|
||||
if (path.empty) return undefined;
|
||||
const tail = path.tail;
|
||||
|
||||
function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); }
|
||||
function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); }
|
||||
|
||||
let symbol: Symbol = undefined;
|
||||
let span: Span = undefined;
|
||||
@ -189,13 +192,18 @@ export class TypeDiagnostic {
|
||||
class AstType implements ExpressionVisitor {
|
||||
public diagnostics: TypeDiagnostic[];
|
||||
|
||||
constructor(private scope: SymbolTable, private query: SymbolQuery) {}
|
||||
constructor(
|
||||
private scope: SymbolTable, private query: SymbolQuery,
|
||||
private context: ExpressionDiagnosticsContext) {}
|
||||
|
||||
getType(ast: AST): Symbol { return ast.visit(this); }
|
||||
|
||||
getDiagnostics(ast: AST): TypeDiagnostic[] {
|
||||
this.diagnostics = [];
|
||||
ast.visit(this);
|
||||
const type: Symbol = ast.visit(this);
|
||||
if (this.context.event && type.callable) {
|
||||
this.reportWarning('Unexpected callable expression. Expected a method call', ast);
|
||||
}
|
||||
return this.diagnostics;
|
||||
}
|
||||
|
||||
@ -751,7 +759,7 @@ function refinedVariableType(
|
||||
const ngForOfBinding = ngForDirective.inputs.find(i => i.directiveName == 'ngForOf');
|
||||
if (ngForOfBinding) {
|
||||
const bindingType =
|
||||
new AstType(info.template.members, info.template.query).getType(ngForOfBinding.value);
|
||||
new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value);
|
||||
if (bindingType) {
|
||||
return info.template.query.getElementType(bindingType);
|
||||
}
|
||||
|
@ -29,17 +29,7 @@ import {createLanguageService} from './language_service';
|
||||
import {ReflectorHost} from './reflector_host';
|
||||
import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types';
|
||||
|
||||
// In TypeScript 2.1 these flags moved
|
||||
// These helpers work for both 2.0 and 2.1.
|
||||
const isPrivate = (ts as any).ModifierFlags ?
|
||||
((node: ts.Node) =>
|
||||
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Private)) :
|
||||
((node: ts.Node) => !!(node.flags & (ts as any).NodeFlags.Private));
|
||||
const isReferenceType = (ts as any).ObjectFlags ?
|
||||
((type: ts.Type) =>
|
||||
!!(type.flags & (ts as any).TypeFlags.Object &&
|
||||
(type as any).objectFlags & (ts as any).ObjectFlags.Reference)) :
|
||||
((type: ts.Type) => !!(type.flags & (ts as any).TypeFlags.Reference));
|
||||
|
||||
|
||||
/**
|
||||
* Create a `LanguageServiceHost`
|
||||
@ -689,7 +679,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
|
||||
const constructorDeclaration = constructor.declarations[0] as ts.ConstructorTypeNode;
|
||||
for (const parameter of constructorDeclaration.parameters) {
|
||||
const type = this.checker.getTypeAtLocation(parameter.type);
|
||||
if (type.symbol.name == 'TemplateRef' && isReferenceType(type)) {
|
||||
if (type.symbol.name == 'TemplateRef' && type.flags & ts.TypeFlags.Reference) {
|
||||
const typeReference = type as ts.TypeReference;
|
||||
if (typeReference.typeArguments.length === 1) {
|
||||
return typeReference.typeArguments[0].symbol;
|
||||
@ -814,7 +804,7 @@ class SymbolWrapper implements Symbol {
|
||||
|
||||
get public(): boolean {
|
||||
// Symbols that are not explicitly made private are public.
|
||||
return !isSymbolPrivate(this.symbol);
|
||||
return !(getDeclarationFlagsFromSymbol(this.symbol) & ts.NodeFlags.Private);
|
||||
}
|
||||
|
||||
get callable(): boolean { return typeCallable(this.tsType); }
|
||||
@ -1106,8 +1096,10 @@ function getCombinedNodeFlags(node: ts.Node): ts.NodeFlags {
|
||||
return flags;
|
||||
}
|
||||
|
||||
function isSymbolPrivate(s: ts.Symbol): boolean {
|
||||
return s.valueDeclaration && isPrivate(s.valueDeclaration);
|
||||
function getDeclarationFlagsFromSymbol(s: ts.Symbol): ts.NodeFlags {
|
||||
return s.valueDeclaration ?
|
||||
getCombinedNodeFlags(s.valueDeclaration) :
|
||||
s.flags & ts.SymbolFlags.Prototype ? ts.NodeFlags.Public | ts.NodeFlags.Static : 0;
|
||||
}
|
||||
|
||||
function getBuiltinTypeFromTs(kind: BuiltinType, context: TypeContext): ts.Type {
|
||||
@ -1265,4 +1257,4 @@ function typeKindOf(type: ts.Type): BuiltinType {
|
||||
}
|
||||
}
|
||||
return BuiltinType.Other;
|
||||
}
|
||||
}
|
@ -10,16 +10,17 @@ import 'reflect-metadata';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Completions} from '../src/types';
|
||||
import {Completions, Diagnostic, Diagnostics} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
|
||||
|
||||
describe('completions', () => {
|
||||
let documentRegistry = ts.createDocumentRegistry();
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
||||
let program = service.getProgram();
|
||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||
let ngService = createLanguageService(ngHost);
|
||||
ngHost.setSite(ngService);
|
||||
|
@ -9,7 +9,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Span} from '../src/types';
|
||||
import {Completions, Diagnostic, Diagnostics, Span} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
@ -20,6 +20,7 @@ describe('definitions', () => {
|
||||
let documentRegistry = ts.createDocumentRegistry();
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
||||
let program = service.getProgram();
|
||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||
let ngService = createLanguageService(ngHost);
|
||||
ngHost.setSite(ngService);
|
||||
|
@ -9,7 +9,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Diagnostics} from '../src/types';
|
||||
import {Completions, Diagnostic, Diagnostics} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
@ -19,6 +19,7 @@ describe('diagnostics', () => {
|
||||
let documentRegistry = ts.createDocumentRegistry();
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
||||
let program = service.getProgram();
|
||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||
let ngService = createLanguageService(ngHost);
|
||||
ngHost.setSite(ngService);
|
||||
@ -129,6 +130,39 @@ describe('diagnostics', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should report a warning if an event results in a callable expression', () => {
|
||||
const code =
|
||||
` @Component({template: \`<div (click)="onClick"></div>\`}) export class MyComponent { onClick() { } }`;
|
||||
addCode(code, (fileName, content) => {
|
||||
const diagnostics = ngService.getDiagnostics(fileName);
|
||||
includeDiagnostic(
|
||||
diagnostics, 'Unexpected callable expression. Expected a method call', 'onClick',
|
||||
content);
|
||||
});
|
||||
});
|
||||
|
||||
// #13412
|
||||
it('should not report an error for using undefined', () => {
|
||||
const code =
|
||||
` @Component({template: \`<div *ngIf="something === undefined"></div>\`}) export class MyComponent { something = 'foo'; }})`;
|
||||
addCode(code, fileName => {
|
||||
const diagnostics = ngService.getDiagnostics(fileName);
|
||||
onlyModuleDiagnostics(diagnostics);
|
||||
});
|
||||
});
|
||||
|
||||
// Issue #13326
|
||||
it('should report a narrow span for invalid pipes', () => {
|
||||
const code =
|
||||
` @Component({template: '<p> Using an invalid pipe {{data | dat}} </p>'}) export class MyComponent { data = 'some data'; }`;
|
||||
addCode(code, fileName => {
|
||||
const diagnostic =
|
||||
ngService.getDiagnostics(fileName).filter(d => d.message.indexOf('pipe') > 0)[0];
|
||||
expect(diagnostic).not.toBeUndefined();
|
||||
expect(diagnostic.span.end - diagnostic.span.start).toBeLessThan(11);
|
||||
});
|
||||
});
|
||||
|
||||
function addCode(code: string, cb: (fileName: string, content?: string) => void) {
|
||||
const fileName = '/app/app.component.ts';
|
||||
const originalContent = mockHost.getFileContent(fileName);
|
||||
|
@ -11,16 +11,17 @@ import 'reflect-metadata';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createLanguageService} from '../src/language_service';
|
||||
import {Hover} from '../src/types';
|
||||
import {Hover, HoverTextSection} from '../src/types';
|
||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
|
||||
import {toh} from './test_data';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
|
||||
|
||||
describe('hover', () => {
|
||||
let documentRegistry = ts.createDocumentRegistry();
|
||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
||||
let program = service.getProgram();
|
||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||
let ngService = createLanguageService(ngHost);
|
||||
ngHost.setSite(ngService);
|
||||
|
@ -213,7 +213,7 @@ export class UnknownTrackBy {
|
||||
'ng-if-cases.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({template: '<div ~{implicit}*ngIf="show; let l"~{implicit-end}>Showing now!</div>'})
|
||||
@Component({template: '<div ~{implicit}*ngIf="show; let l=unknown"~{implicit-end}>Showing now!</div>'})
|
||||
export class ShowIf {
|
||||
show = false;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export type MockData = string | MockDirectory;
|
||||
|
||||
export type MockDirectory = {
|
||||
[name: string]: MockData | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
|
||||
const rxjsts = /rxjs\/(\w|\/)+\.tsx?$/;
|
||||
|
@ -186,7 +186,8 @@ describe('plugin', () => {
|
||||
expectSemanticError('app/ng-if-cases.ts', locationMarker, message);
|
||||
}
|
||||
it('should report an implicit context reference', () => {
|
||||
expectError('implicit', 'The template context does not have an implicit value');
|
||||
expectError(
|
||||
'implicit', 'The template context does not defined a member called \'unknown\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from './platform_providers';
|
||||
import * as resource_loader from './resource_loader/resource_loader_impl';
|
||||
|
||||
export const __platform_browser_dynamic_private__: {
|
||||
export var __platform_browser_dynamic_private__: {
|
||||
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: typeof INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
|
||||
_ResourceLoaderImpl?: resource_loader.ResourceLoaderImpl,
|
||||
ResourceLoaderImpl: typeof resource_loader.ResourceLoaderImpl
|
||||
|
@ -8,6 +8,6 @@
|
||||
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export const reflector: typeof r.reflector = r.reflector;
|
||||
export const Console: typeof r.Console = r.Console;
|
||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export var reflector: typeof r.reflector = r.reflector;
|
||||
export var Console: typeof r.Console = r.Console;
|
||||
|
@ -8,6 +8,6 @@
|
||||
|
||||
import {__platform_browser_private__ as _} from '@angular/platform-browser';
|
||||
|
||||
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
|
||||
export var INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
|
||||
_.INTERNAL_BROWSER_PLATFORM_PROVIDERS;
|
||||
export const getDOM: typeof _.getDOM = _.getDOM;
|
||||
export var getDOM: typeof _.getDOM = _.getDOM;
|
||||
|
@ -11,6 +11,8 @@ import {TestComponentRenderer} from '@angular/core/testing';
|
||||
import {DOCUMENT} from '@angular/platform-browser';
|
||||
import {getDOM} from './private_import_platform-browser';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A DOM based implementation of the TestComponentRenderer.
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as testing from './dom_test_component_renderer';
|
||||
|
||||
export const __platform_browser_dynamic_private__:
|
||||
export var __platform_browser_dynamic_private__:
|
||||
{DOMTestComponentRenderer: typeof testing.DOMTestComponentRenderer} = {
|
||||
DOMTestComponentRenderer: testing.DOMTestComponentRenderer
|
||||
};
|
||||
|
@ -8,6 +8,6 @@
|
||||
|
||||
import {__platform_browser_dynamic_private__ as _} from '@angular/platform-browser-dynamic';
|
||||
|
||||
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
|
||||
export var INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
|
||||
typeof _.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS =
|
||||
_.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS;
|
||||
|
@ -8,4 +8,4 @@
|
||||
|
||||
import {__platform_browser_private__ as _} from '@angular/platform-browser';
|
||||
|
||||
export const getDOM: typeof _.getDOM = _.getDOM;
|
||||
export var getDOM: typeof _.getDOM = _.getDOM;
|
||||
|
@ -14,6 +14,7 @@ import {WebAnimationsDriver} from '../src/dom/web_animations_driver';
|
||||
|
||||
import {BrowserDomAdapter} from './browser/browser_adapter';
|
||||
import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
|
||||
import {Meta} from './browser/meta';
|
||||
import {BrowserGetTestability} from './browser/testability';
|
||||
import {Title} from './browser/title';
|
||||
import {ELEMENT_PROBE_PROVIDERS} from './dom/debug/ng_probe';
|
||||
@ -46,7 +47,7 @@ export const BROWSER_SANITIZATION_PROVIDERS: Array<any> = [
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export const platformBrowser =
|
||||
export const platformBrowser: (extraProviders?: Provider[]) => PlatformRef =
|
||||
createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);
|
||||
|
||||
export function initDomAdapter() {
|
||||
@ -58,6 +59,10 @@ export function errorHandler(): ErrorHandler {
|
||||
return new ErrorHandler();
|
||||
}
|
||||
|
||||
export function meta(): Meta {
|
||||
return new Meta(getDOM());
|
||||
}
|
||||
|
||||
export function _document(): any {
|
||||
return getDOM().defaultDoc();
|
||||
}
|
||||
@ -76,7 +81,8 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
*/
|
||||
@NgModule({
|
||||
providers: [
|
||||
BROWSER_SANITIZATION_PROVIDERS, {provide: ErrorHandler, useFactory: errorHandler, deps: []},
|
||||
BROWSER_SANITIZATION_PROVIDERS,
|
||||
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
|
||||
{provide: DOCUMENT, useFactory: _document, deps: []},
|
||||
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
|
||||
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
|
||||
@ -85,8 +91,13 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver}, DomSharedStylesHost,
|
||||
Testability, EventManager, ELEMENT_PROBE_PROVIDERS, Title
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
{provide: Meta, useFactory: meta},
|
||||
DomSharedStylesHost,
|
||||
Testability,
|
||||
EventManager,
|
||||
ELEMENT_PROBE_PROVIDERS,
|
||||
Title,
|
||||
],
|
||||
exports: [CommonModule, ApplicationModule]
|
||||
})
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user