build: reformat repo to new clang@1.4.0 (#36628)

PR Close #36628
This commit is contained in:
Joey Perrott
2020-04-13 17:43:52 -07:00
committed by atscott
parent 4b3f9ac739
commit 26f49151e7
1163 changed files with 31727 additions and 24036 deletions

View File

@ -6,18 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/
import {COMPILER_OPTIONS, Component, NgModule, ViewEncapsulation, destroyPlatform} from '@angular/core';
import {COMPILER_OPTIONS, Component, destroyPlatform, NgModule, ViewEncapsulation} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {onlyInIvy, withBody} from '@angular/private/testing';
describe('bootstrap', () => {
beforeEach(destroyPlatform);
afterEach(destroyPlatform);
it('should bootstrap using #id selector',
withBody('<div>before|</div><button id="my-app"></button>', async() => {
withBody('<div>before|</div><button id="my-app"></button>', async () => {
try {
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(IdSelectorAppModule);
expect(document.body.textContent).toEqual('before|works!');
@ -28,7 +27,7 @@ describe('bootstrap', () => {
}));
it('should bootstrap using one of selectors from the list',
withBody('<div>before|</div><div class="bar"></div>', async() => {
withBody('<div>before|</div><div class="bar"></div>', async () => {
try {
const ngModuleRef =
await platformBrowserDynamic().bootstrapModule(MultipleSelectorsAppModule);
@ -66,7 +65,7 @@ describe('bootstrap', () => {
}
it('should use ViewEncapsulation.Emulated as default',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
@ -75,7 +74,7 @@ describe('bootstrap', () => {
}));
it('should allow setting defaultEncapsulation using bootstrap option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
@ -86,7 +85,7 @@ describe('bootstrap', () => {
}));
it('should allow setting defaultEncapsulation using compiler option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic([{
@ -100,7 +99,7 @@ describe('bootstrap', () => {
}));
it('should prefer encapsulation on component over bootstrap option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule({encapsulation: ViewEncapsulation.Emulated});
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
@ -110,7 +109,7 @@ describe('bootstrap', () => {
}));
it('should use preserveWhitespaces: false as default',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
@ -119,7 +118,7 @@ describe('bootstrap', () => {
}));
it('should allow setting preserveWhitespaces using bootstrap option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
@ -129,7 +128,7 @@ describe('bootstrap', () => {
}));
it('should allow setting preserveWhitespaces using compiler option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef =
@ -141,7 +140,7 @@ describe('bootstrap', () => {
}));
it('should prefer preserveWhitespaces on component over bootstrap option',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule({preserveWhitespaces: false});
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
@ -151,10 +150,12 @@ describe('bootstrap', () => {
}));
onlyInIvy('options cannot be changed in Ivy').describe('changing bootstrap options', () => {
beforeEach(() => { spyOn(console, 'error'); });
beforeEach(() => {
spyOn(console, 'error');
});
it('should log an error when changing defaultEncapsulation bootstrap options',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const platformRef = platformBrowserDynamic();
@ -175,7 +176,7 @@ describe('bootstrap', () => {
}));
it('should log an error when changing preserveWhitespaces bootstrap options',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const platformRef = platformBrowserDynamic();
@ -196,7 +197,7 @@ describe('bootstrap', () => {
}));
it('should log an error when changing defaultEncapsulation to its default',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const platformRef = platformBrowserDynamic();
@ -215,7 +216,7 @@ describe('bootstrap', () => {
}));
it('should log an error when changing preserveWhitespaces to its default',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const platformRef = platformBrowserDynamic();
@ -234,7 +235,7 @@ describe('bootstrap', () => {
}));
it('should not log an error when passing identical bootstrap options',
withBody('<my-app></my-app>', async() => {
withBody('<my-app></my-app>', async () => {
const TestModule = createComponentAndModule();
const platformRef = platformBrowserDynamic();

View File

@ -16,16 +16,16 @@ import {ivyEnabled} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';
describe('change detection', () => {
describe('embedded views', () => {
@Directive({selector: '[viewManipulation]', exportAs: 'vm'})
class ViewManipulation {
constructor(
private _tplRef: TemplateRef<{}>, public vcRef: ViewContainerRef,
private _appRef: ApplicationRef) {}
insertIntoVcRef() { return this.vcRef.createEmbeddedView(this._tplRef); }
insertIntoVcRef() {
return this.vcRef.createEmbeddedView(this._tplRef);
}
insertIntoAppRef(): EmbeddedViewRef<{}> {
const viewRef = this._tplRef.createEmbeddedView({});
@ -80,7 +80,9 @@ describe('change detection', () => {
changeDetection: ChangeDetectionStrategy.OnPush
})
class App {
increment(counter: 'componentView'|'embeddedView') { counters[counter]++; }
increment(counter: 'componentView'|'embeddedView') {
counters[counter]++;
}
noop() {}
}
@ -129,7 +131,9 @@ describe('change detection', () => {
changeDetection: ChangeDetectionStrategy.OnPush
})
class DynamicComp {
increment() { counter++; }
increment() {
counter++;
}
noop() {}
}
@ -170,13 +174,10 @@ describe('change detection', () => {
expect(counter).toBe(3);
});
});
describe('markForCheck', () => {
it('should mark OnPush ancestor of dynamically created component views as dirty', () => {
@Component({
selector: `test-cmpt`,
template: `{{counter}}|<ng-template #vc></ng-template>`,
@ -184,7 +185,7 @@ describe('change detection', () => {
})
class TestCmpt {
counter = 0;
@ViewChild('vc', {read: ViewContainerRef}) vcRef !: ViewContainerRef;
@ViewChild('vc', {read: ViewContainerRef}) vcRef!: ViewContainerRef;
constructor(private _cfr: ComponentFactoryResolver) {}
@ -252,11 +253,13 @@ describe('change detection', () => {
/**
* @internal
*/
private _input !: number;
private _input!: number;
constructor(private cdr: ChangeDetectorRef) {}
get input() { return this._input; }
get input() {
return this._input;
}
set input(value: number) {
this._input = value;
@ -286,18 +289,19 @@ describe('change detection', () => {
template: `{{ doCheckCount }} - {{ name }} <button (click)="onClick()"></button>`
})
class MyComponent implements DoCheck {
@Input()
name = 'Nancy';
@Input() name = 'Nancy';
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
onClick() {}
}
@Component({selector: 'my-app', template: '<my-comp [name]="name"></my-comp>'})
class MyApp {
@ViewChild(MyComponent) comp !: MyComponent;
@ViewChild(MyComponent) comp!: MyComponent;
name: string = 'Nancy';
}
@ -369,7 +373,7 @@ describe('change detection', () => {
expect(fixture.componentInstance.comp.doCheckCount).toEqual(1);
expect(fixture.nativeElement.textContent.trim()).toEqual('1 - Nancy');
const button = fixture.nativeElement.querySelector('button') !;
const button = fixture.nativeElement.querySelector('button')!;
button.click();
// No ticks should have been scheduled.
@ -389,7 +393,7 @@ describe('change detection', () => {
template: '<my-comp></my-comp><button id="parent" (click)="noop()"></button>'
})
class ButtonParent {
@ViewChild(MyComponent) comp !: MyComponent;
@ViewChild(MyComponent) comp!: MyComponent;
noop() {}
}
@ -415,16 +419,18 @@ describe('change detection', () => {
changeDetection: ChangeDetectionStrategy.OnPush
})
class ButtonParent implements DoCheck {
@ViewChild(MyComponent) comp !: MyComponent;
@ViewChild(MyComponent) comp!: MyComponent;
noop() {}
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
}
@Component({selector: 'my-button-app', template: '<button-parent></button-parent>'})
class MyButtonApp {
@ViewChild(ButtonParent) parent !: ButtonParent;
@ViewChild(ButtonParent) parent!: ButtonParent;
}
TestBed.configureTestingModule({declarations: [MyButtonApp, MyComponent, ButtonParent]});
@ -456,7 +462,6 @@ describe('change detection', () => {
expect(comp.doCheckCount).toEqual(2);
expect(fixture.nativeElement.textContent.trim()).toEqual('3 - 2 - Nancy');
});
});
describe('ChangeDetectorRef', () => {
@ -472,18 +477,22 @@ describe('change detection', () => {
constructor(public cdr: ChangeDetectorRef) {}
ngDoCheck() { this.doCheckCount++; }
ngDoCheck() {
this.doCheckCount++;
}
}
@Component({selector: 'parent-comp', template: `{{ doCheckCount}} - <my-comp></my-comp>`})
class ParentComp implements DoCheck {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(MyComp) myComp!: MyComp;
doCheckCount = 0;
constructor(public cdr: ChangeDetectorRef) {}
ngDoCheck() { this.doCheckCount++; }
ngDoCheck() {
this.doCheckCount++;
}
}
@Directive({selector: '[dir]'})
@ -562,8 +571,8 @@ describe('change detection', () => {
it('should check component view when called by directive on component node', () => {
@Component({template: '<my-comp dir></my-comp>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(Dir) dir !: Dir;
@ViewChild(MyComp) myComp!: MyComp;
@ViewChild(Dir) dir!: Dir;
}
TestBed.configureTestingModule({declarations: [MyComp, Dir, MyApp]});
@ -580,8 +589,8 @@ describe('change detection', () => {
it('should check host component when called by directive on element node', () => {
@Component({template: '{{ value }}<div dir></div>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(Dir) dir !: Dir;
@ViewChild(MyComp) myComp!: MyComp;
@ViewChild(Dir) dir!: Dir;
value = '';
}
@ -601,7 +610,7 @@ describe('change detection', () => {
it('should check the host component when called from EmbeddedViewRef', () => {
@Component({template: '{{ name }}<div *ngIf="showing" dir></div>'})
class MyApp {
@ViewChild(Dir) dir !: Dir;
@ViewChild(Dir) dir!: Dir;
showing = true;
name = 'Amelia';
}
@ -642,21 +651,30 @@ describe('change detection', () => {
@Component({template: '<child-comp [inp]="true"></child-comp>'})
class ParentComp {
constructor(public cdr: ChangeDetectorRef) {}
triggerChangeDetection() { this.cdr.detectChanges(); }
triggerChangeDetection() {
this.cdr.detectChanges();
}
}
@Component({template: '{{inp}}', selector: 'child-comp'})
class ChildComp {
@Input()
inp: any = '';
@Input() inp: any = '';
count = 0;
constructor(public parentComp: ParentComp) {}
ngOnInit() { this.check('OnInit'); }
ngAfterContentInit() { this.check('AfterContentInit'); }
ngAfterViewInit() { this.check('AfterViewInit'); }
ngOnChanges() { this.check('OnChanges'); }
ngOnInit() {
this.check('OnInit');
}
ngAfterContentInit() {
this.check('AfterContentInit');
}
ngAfterViewInit() {
this.check('AfterViewInit');
}
ngOnChanges() {
this.check('OnChanges');
}
check(h: string) {
if (h === hook) {
@ -704,8 +722,7 @@ describe('change detection', () => {
`
})
class App {
@ViewChildren('ref')
ref !: QueryList<any>;
@ViewChildren('ref') ref!: QueryList<any>;
visible = false;
@ -737,13 +754,14 @@ describe('change detection', () => {
describe('dynamic views', () => {
@Component({selector: 'structural-comp', template: '{{ value }}'})
class StructuralComp {
@Input()
tmp !: TemplateRef<any>;
@Input() tmp!: TemplateRef<any>;
value = 'one';
constructor(public vcr: ViewContainerRef) {}
create() { return this.vcr.createEmbeddedView(this.tmp, {ctx: this}); }
create() {
return this.vcr.createEmbeddedView(this.tmp, {ctx: this});
}
}
it('should support ViewRef.detectChanges()', () => {
@ -752,7 +770,7 @@ describe('change detection', () => {
'<ng-template #foo let-ctx="ctx">{{ ctx.value }}</ng-template><structural-comp [tmp]="foo"></structural-comp>'
})
class App {
@ViewChild(StructuralComp) structuralComp !: StructuralComp;
@ViewChild(StructuralComp) structuralComp!: StructuralComp;
}
TestBed.configureTestingModule({declarations: [App, StructuralComp]});
@ -781,7 +799,7 @@ describe('change detection', () => {
template: '<ng-template #foo>Template text</ng-template><structural-comp [tmp]="foo">'
})
class App {
@ViewChild(StructuralComp) structuralComp !: StructuralComp;
@ViewChild(StructuralComp) structuralComp!: StructuralComp;
}
TestBed.configureTestingModule({declarations: [App, StructuralComp]});
@ -794,9 +812,7 @@ describe('change detection', () => {
viewRef.detectChanges();
expect(fixture.nativeElement.textContent).toEqual('oneTemplate text');
});
});
});
describe('attach/detach', () => {
@ -807,12 +823,14 @@ describe('change detection', () => {
constructor(public cdr: ChangeDetectorRef) {}
ngDoCheck() { this.doCheckCount++; }
ngDoCheck() {
this.doCheckCount++;
}
}
@Component({template: '<detached-comp></detached-comp>'})
class MyApp {
@ViewChild(DetachedComp) comp !: DetachedComp;
@ViewChild(DetachedComp) comp!: DetachedComp;
constructor(public cdr: ChangeDetectorRef) {}
}
@ -908,22 +926,20 @@ describe('change detection', () => {
});
it('should detach OnPush components properly', () => {
@Component({
selector: 'on-push-comp',
template: '{{ value }}',
changeDetection: ChangeDetectionStrategy.OnPush
})
class OnPushComp {
@Input()
value !: string;
@Input() value!: string;
constructor(public cdr: ChangeDetectorRef) {}
}
@Component({template: '<on-push-comp [value]="value"></on-push-comp>'})
class OnPushApp {
@ViewChild(OnPushComp) onPushComp !: OnPushComp;
@ViewChild(OnPushComp) onPushComp!: OnPushComp;
value = '';
}
@ -946,7 +962,6 @@ describe('change detection', () => {
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toEqual('two');
});
});
describe('markForCheck()', () => {
@ -962,7 +977,9 @@ describe('change detection', () => {
constructor(public cdr: ChangeDetectorRef) {}
ngDoCheck() { this.doCheckCount++; }
ngDoCheck() {
this.doCheckCount++;
}
}
@Component({
@ -970,7 +987,7 @@ describe('change detection', () => {
changeDetection: ChangeDetectionStrategy.OnPush
})
class OnPushParent {
@ViewChild(OnPushComp) comp !: OnPushComp;
@ViewChild(OnPushComp) comp!: OnPushComp;
value = 'one';
}
@ -1023,7 +1040,6 @@ describe('change detection', () => {
fixture.componentInstance.comp.cdr.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toEqual('two - one');
});
it('should ensure OnPush components in embedded views are checked', () => {
@ -1032,7 +1048,7 @@ describe('change detection', () => {
changeDetection: ChangeDetectionStrategy.OnPush
})
class EmbeddedViewParent {
@ViewChild(OnPushComp) comp !: OnPushComp;
@ViewChild(OnPushComp) comp!: OnPushComp;
value = 'one';
showing = true;
}
@ -1112,13 +1128,21 @@ describe('change detection', () => {
contentCheckCount = 0;
viewCheckCount = 0;
ngDoCheck() { this.doCheckCount++; }
ngDoCheck() {
this.doCheckCount++;
}
ngAfterContentChecked() { this.contentCheckCount++; }
ngAfterContentChecked() {
this.contentCheckCount++;
}
ngAfterViewChecked() { this.viewCheckCount++; }
ngAfterViewChecked() {
this.viewCheckCount++;
}
constructor(public cdr: ChangeDetectorRef) { comp = this; }
constructor(public cdr: ChangeDetectorRef) {
comp = this;
}
}
@Component({template: '{{ value }} - <no-changes-comp></no-changes-comp>'})
@ -1131,7 +1155,9 @@ describe('change detection', () => {
// Custom error handler that just rethrows all the errors from the
// view, rather than logging them out. Used to keep our logs clean.
class RethrowErrorHandler extends ErrorHandler {
handleError(error: any) { throw error; }
handleError(error: any) {
throw error;
}
}
it('should throw if bindings in current view have changed', () => {
@ -1141,7 +1167,9 @@ describe('change detection', () => {
});
const fixture = TestBed.createComponent(NoChangesComp);
expect(() => { fixture.componentInstance.cdr.checkNoChanges(); })
expect(() => {
fixture.componentInstance.cdr.checkNoChanges();
})
.toThrowError(
/ExpressionChangedAfterItHasBeenCheckedError: .+ Previous value: '.*undefined'. Current value: '.*1'/gi);
});
@ -1196,9 +1224,7 @@ describe('change detection', () => {
expect(comp.contentCheckCount).toEqual(1);
expect(comp.viewCheckCount).toEqual(1);
});
});
});
describe('transplanted views', () => {
@ -1216,13 +1242,20 @@ describe('change detection', () => {
</div>
`
})
class InsertComp implements DoCheck,
AfterViewChecked {
get template(): TemplateRef<any> { return declareComp.myTmpl; }
class InsertComp implements DoCheck, AfterViewChecked {
get template(): TemplateRef<any> {
return declareComp.myTmpl;
}
greeting: string = 'Hello';
constructor(public changeDetectorRef: ChangeDetectorRef) { insertComp = this; }
ngDoCheck(): void { logValue = 'Insert'; }
ngAfterViewChecked(): void { logValue = null; }
constructor(public changeDetectorRef: ChangeDetectorRef) {
insertComp = this;
}
ngDoCheck(): void {
logValue = 'Insert';
}
ngAfterViewChecked(): void {
logValue = null;
}
}
@Component({
@ -1234,20 +1267,24 @@ describe('change detection', () => {
</ng-template>
`
})
class DeclareComp implements DoCheck,
AfterViewChecked {
@ViewChild('myTmpl')
myTmpl !: TemplateRef<any>;
class DeclareComp implements DoCheck, AfterViewChecked {
@ViewChild('myTmpl') myTmpl!: TemplateRef<any>;
name: string = 'world';
constructor() { declareComp = this; }
ngDoCheck(): void { logValue = 'Declare'; }
constructor() {
declareComp = this;
}
ngDoCheck(): void {
logValue = 'Declare';
}
logName() {
// This will log when the embedded view gets CD. The `logValue` will show if the CD was from
// `Insert` or from `Declare` component.
log.push(logValue !);
log.push(logValue!);
return this.name;
}
ngAfterViewChecked(): void { logValue = null; }
ngAfterViewChecked(): void {
logValue = null;
}
}
@Component({
@ -1259,15 +1296,17 @@ describe('change detection', () => {
class AppComp {
showDeclare: boolean = true;
showInsert: boolean = true;
constructor() { appComp = this; }
constructor() {
appComp = this;
}
}
let log !: string[];
let logValue !: string | null;
let fixture !: ComponentFixture<AppComp>;
let appComp !: AppComp;
let insertComp !: InsertComp;
let declareComp !: DeclareComp;
let log!: string[];
let logValue!: string|null;
let fixture!: ComponentFixture<AppComp>;
let appComp!: AppComp;
let insertComp!: InsertComp;
let declareComp!: DeclareComp;
beforeEach(() => {
TestBed.configureTestingModule({
@ -1376,16 +1415,16 @@ describe('change detection', () => {
class OnPushComp {
text = 'initial';
constructor(private _cdRef: ChangeDetectorRef){}
constructor(private _cdRef: ChangeDetectorRef) {}
[hookName]() {
[hookName]() {
this._cdRef.markForCheck();
}
}
@Component({template: `<on-push-comp></on-push-comp>`})
class TestApp {
@ViewChild(OnPushComp) onPushComp !: OnPushComp;
@ViewChild(OnPushComp) onPushComp!: OnPushComp;
}
TestBed.configureTestingModule(
@ -1419,16 +1458,16 @@ describe('change detection', () => {
class OnPushComp {
text = 'initial';
constructor(private _cdRef: ChangeDetectorRef){}
constructor(private _cdRef: ChangeDetectorRef) {}
[hookName]() {
[hookName]() {
this._cdRef.markForCheck();
}
}
@Component({template: `<on-push-comp></on-push-comp>`})
class TestApp {
@ViewChild(OnPushComp) onPushComp !: OnPushComp;
@ViewChild(OnPushComp) onPushComp!: OnPushComp;
}
TestBed.configureTestingModule(
@ -1478,7 +1517,9 @@ describe('change detection', () => {
return fixture;
}
function initWithTemplate(template: string) { return initComponent({template}); }
function initWithTemplate(template: string) {
return initComponent({template});
}
function initWithHostBindings(bindings: {[key: string]: string}) {
return initComponent({host: bindings});
}
@ -1612,6 +1653,6 @@ describe('change detection', () => {
});
});
function trim(text: string | null): string {
function trim(text: string|null): string {
return text ? text.replace(/[\s\n]+/gm, ' ').trim() : '';
}

View File

@ -11,7 +11,6 @@ import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
describe('@angular/common integration', () => {
describe('NgForOf', () => {
@Directive({selector: '[dir]'})
class MyDirective {
@ -244,7 +243,9 @@ describe('@angular/common integration', () => {
name = 'app';
events: string[] = [];
onClick(value: string, name: string) { this.events.push(value, name); }
onClick(value: string, name: string) {
this.events.push(value, name);
}
}
TestBed.configureTestingModule({declarations: [MultiLevelWithListenerComponent]});
@ -485,7 +486,6 @@ describe('@angular/common integration', () => {
});
describe('NgTemplateOutlet', () => {
it('should create and remove embedded views', () => {
@Component({
selector: 'app-multi',

View File

@ -28,7 +28,9 @@ describe('component', () => {
providers: [{provide: testToken, useExisting: ParentWithOnDestroy}]
})
class ParentWithOnDestroy {
ngOnDestroy() { destroyCalls++; }
ngOnDestroy() {
destroyCalls++;
}
}
@Component({selector: 'child', template: ''})
@ -75,7 +77,7 @@ describe('component', () => {
entryComponents: [OtherComponent]
})
class TestComponent {
@ViewChild('vc', {read: ViewContainerRef, static: true}) vcref !: ViewContainerRef;
@ViewChild('vc', {read: ViewContainerRef, static: true}) vcref!: ViewContainerRef;
constructor(private _cfr: ComponentFactoryResolver) {}
@ -152,7 +154,8 @@ describe('component', () => {
expect(match).toBeDefined();
expect(match.length).toEqual(2);
expect(html).toMatch(
`<leaf ${match[0].replace('_nghost', '_ngcontent')}="" ${match[1]}=""><span ${match[1].replace('_nghost', '_ngcontent')}="">bar</span></leaf></div>`);
`<leaf ${match[0].replace('_nghost', '_ngcontent')}="" ${match[1]}=""><span ${
match[1].replace('_nghost', '_ngcontent')}="">bar</span></leaf></div>`);
});
});
@ -162,7 +165,9 @@ describe('component', () => {
@Component({selector: 'comp-with-destroy', template: ``})
class ComponentWithOnDestroy implements OnDestroy {
ngOnDestroy() { wasOnDestroyCalled = true; }
ngOnDestroy() {
wasOnDestroyCalled = true;
}
}
// This test asserts that the view tree is set up correctly based on the knowledge that this
@ -244,7 +249,7 @@ describe('component', () => {
encapsulation: ViewEncapsulation.Emulated,
})
class Parent {
@ViewChild(Child) childInstance !: Child;
@ViewChild(Child) childInstance!: Child;
constructor(public renderer: Renderer2) {}
}
@ -266,7 +271,9 @@ describe('component', () => {
})
class CompA {
@Input() a: string = '';
ngDoCheck() { log.push('CompA:ngDoCheck'); }
ngDoCheck() {
log.push('CompA:ngDoCheck');
}
}
@Component({
@ -275,7 +282,9 @@ describe('component', () => {
})
class CompB {
@Input() b: string = '';
ngDoCheck() { log.push('CompB:ngDoCheck'); }
ngDoCheck() {
log.push('CompB:ngDoCheck');
}
}
@Component({template: `<span></span>`})
@ -435,14 +444,14 @@ describe('component', () => {
fixture.detectChanges();
// Create an instance of DynamicComponent and provide host element *reference*
let targetEl = document.getElementById('dynamic-comp-root-a') !;
let targetEl = document.getElementById('dynamic-comp-root-a')!;
fixture.componentInstance.createDynamicComponent(targetEl);
fixture.detectChanges();
expect(targetEl.innerHTML).not.toContain('Existing content in slot A');
expect(targetEl.innerHTML).toContain('DynamicComponent Content');
// Create an instance of DynamicComponent and provide host element *selector*
targetEl = document.getElementById('dynamic-comp-root-b') !;
targetEl = document.getElementById('dynamic-comp-root-b')!;
fixture.componentInstance.createDynamicComponent('#dynamic-comp-root-b');
fixture.detectChanges();
expect(targetEl.innerHTML).not.toContain('Existing content in slot B');
@ -453,7 +462,8 @@ describe('component', () => {
() => runTestWithRenderer([{provide: RendererFactory2, useClass: DomRendererFactory2}]));
onlyInIvy('Renderer3 is supported only in Ivy')
.it('with Renderer3', () => runTestWithRenderer(
[{provide: RendererFactory2, useValue: domRendererFactory3}]));
.it('with Renderer3',
() =>
runTestWithRenderer([{provide: RendererFactory2, useValue: domRendererFactory3}]));
});
});

View File

@ -14,7 +14,6 @@ import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
describe('projection', () => {
function getElementHtml(element: HTMLElement) {
return element.innerHTML.replace(/<!--(\W|\w)*?-->/g, '')
.replace(/\sng-reflect-\S*="[^"]*"/g, '');
@ -328,11 +327,13 @@ describe('projection', () => {
@Directive({selector: '[trigger]'})
class Trigger {
@Input() trigger !: Comp;
@Input() trigger!: Comp;
constructor(public vcr: ViewContainerRef) {}
open() { this.vcr.createEmbeddedView(this.trigger.template); }
open() {
this.vcr.createEmbeddedView(this.trigger.template);
}
}
@Component({
@ -861,7 +862,6 @@ describe('projection', () => {
expect(getElementHtml(fixture.nativeElement))
.toEqual('<child><span title="Some title">Has title</span></child>');
});
it('should match selectors against projected containers', () => {
@ -934,7 +934,9 @@ describe('projection', () => {
it('should project content if the change detector has been detached', () => {
@Component({selector: 'my-comp', template: '<ng-content></ng-content>'})
class MyComp {
constructor(changeDetectorRef: ChangeDetectorRef) { changeDetectorRef.detach(); }
constructor(changeDetectorRef: ChangeDetectorRef) {
changeDetectorRef.detach();
}
}
@Component({
@ -1108,7 +1110,9 @@ describe('projection', () => {
@Directive({selector: 'div'})
class DivDirective {
constructor() { divDirectives++; }
constructor() {
divDirectives++;
}
}
@Component({
@ -1135,7 +1139,9 @@ describe('projection', () => {
@Directive({selector: '[x]'})
class XDirective {
constructor() { xDirectives++; }
constructor() {
xDirectives++;
}
}
@Component({
@ -1162,7 +1168,9 @@ describe('projection', () => {
@Directive({selector: '.x'})
class XDirective {
constructor() { xDirectives++; }
constructor() {
xDirectives++;
}
}
@Component({
@ -1199,7 +1207,9 @@ describe('projection', () => {
{id: 2, name: 'two'},
{id: 3, name: 'three'},
];
getItemId(item: {id: number}) { return item.id; }
getItemId(item: {id: number}) {
return item.id;
}
}
TestBed.configureTestingModule({declarations: [SelectedNgContentComp, SelectorMainComp]});
@ -1265,7 +1275,9 @@ describe('projection', () => {
@Directive({selector: '[x]'})
class XDirective {
constructor() { xDirectives++; }
constructor() {
xDirectives++;
}
}
@Component({
@ -1293,7 +1305,9 @@ describe('projection', () => {
@Directive({selector: '.x'})
class XDirective {
constructor() { xDirectives++; }
constructor() {
xDirectives++;
}
}
@Component({

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule, ɵɵCopyDefinitionFeature as CopyDefinitionFeature, ɵɵInheritDefinitionFeature as InheritDefinitionFeature, ɵɵdefineComponent as defineComponent} from '@angular/core';
import {Component, NgModule, ɵɵCopyDefinitionFeature as CopyDefinitionFeature, ɵɵdefineComponent as defineComponent, ɵɵInheritDefinitionFeature as InheritDefinitionFeature} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {onlyInIvy} from '@angular/private/testing';
@ -14,20 +14,22 @@ describe('Ivy CopyDefinitionFeature', () => {
onlyInIvy('this feature is not required in View Engine')
.it('should copy the template function of a component definition from parent to child',
() => {
// It would be nice if the base component could be JIT compiled. However, this creates
// a getter for ɵcmp which precludes adding a static definition of that field for the
// child class.
// TODO(alxhub): see if there's a cleaner way to do this.
class BaseComponent {
name !: string;
name!: string;
static ɵcmp = defineComponent({
type: BaseComponent,
selectors: [['some-cmp']],
decls: 0,
vars: 0,
inputs: {name: 'name'},
template: function BaseComponent_Template(rf, ctx) { ctx.rendered = true; },
template:
function BaseComponent_Template(rf, ctx) {
ctx.rendered = true;
},
encapsulation: 2
});
static ɵfac = function BaseComponent_Factory(t: any) {

View File

@ -14,9 +14,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
describe('Debug Representation', () => {
onlyInIvy('Ivy specific').it('should generate a human readable version', () => {
@Component({selector: 'my-comp', template: '<div id="123">Hello World</div>'})
class MyComponent {
}
@ -25,11 +23,11 @@ describe('Debug Representation', () => {
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const hostView = toDebug(getLContext(fixture.componentInstance) !.lView);
const hostView = toDebug(getLContext(fixture.componentInstance)!.lView);
expect(hostView.host).toEqual(null);
const myCompView = hostView.childViews[0] as LViewDebug;
expect(myCompView.host).toContain('<div id="123">Hello World</div>');
expect(myCompView.nodes ![0].html).toEqual('<div id="123">');
expect(myCompView.nodes ![0].nodes ![0].html).toEqual('Hello World');
expect(myCompView.nodes![0].html).toEqual('<div id="123">');
expect(myCompView.nodes![0].nodes![0].html).toEqual('Hello World');
});
});

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, forwardRef, Host, HostBinding, Inject, Injectable, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
import {ɵINJECTOR_SCOPE} from '@angular/core/src/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
@ -60,14 +60,15 @@ describe('di', () => {
});
describe('directive injection', () => {
let log: string[] = [];
@Directive({selector: '[dirB]', exportAs: 'dirB'})
class DirectiveB {
@Input() value = 'DirB';
constructor() { log.push(this.value); }
constructor() {
log.push(this.value);
}
}
beforeEach(() => log = []);
@ -82,7 +83,9 @@ describe('di', () => {
class DirectiveC {
value: string;
constructor(dirA: DirectiveA, dirB: DirectiveB) { this.value = dirA.value + dirB.value; }
constructor(dirA: DirectiveA, dirB: DirectiveB) {
this.value = dirA.value + dirB.value;
}
}
@Component({
@ -108,7 +111,9 @@ describe('di', () => {
class DirectiveA {
value = 'dirA';
constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); }
constructor(dirB: DirectiveB) {
log.push(`DirA (dep: ${dirB.value})`);
}
}
@Component({template: '<div dirA dirB></div>'})
@ -127,7 +132,9 @@ describe('di', () => {
class DirectiveA {
value = 'dirA';
constructor(dirB: DirectiveB) { log.push(`DirA (dep: ${dirB.value})`); }
constructor(dirB: DirectiveB) {
log.push(`DirA (dep: ${dirB.value})`);
}
}
// - dirB is know to the node injectors
@ -150,7 +157,9 @@ describe('di', () => {
it('should instantiate injected directives before components', () => {
@Component({selector: 'my-comp', template: ''})
class MyComp {
constructor(dirB: DirectiveB) { log.push(`Comp (dep: ${dirB.value})`); }
constructor(dirB: DirectiveB) {
log.push(`Comp (dep: ${dirB.value})`);
}
}
@Component({template: '<my-comp dirB></my-comp>'})
@ -167,7 +176,9 @@ describe('di', () => {
it('should inject directives in the correct order in a for loop', () => {
@Directive({selector: '[dirA]'})
class DirectiveA {
constructor(dir: DirectiveB) { log.push(`DirA (dep: ${dir.value})`); }
constructor(dir: DirectiveB) {
log.push(`DirA (dep: ${dir.value})`);
}
}
@Component({template: '<div dirA dirB *ngFor="let i of array"></div>'})
@ -188,14 +199,18 @@ describe('di', () => {
class DirectiveA {
value = 'DirA';
constructor() { log.push(this.value); }
constructor() {
log.push(this.value);
}
}
@Directive({selector: '[dirC]'})
class DirectiveC {
value = 'DirC';
constructor() { log.push(this.value); }
constructor() {
log.push(this.value);
}
}
@Directive({selector: '[dirB]'})
@ -221,26 +236,34 @@ describe('di', () => {
class DirectiveC {
value = 'DirC';
constructor(dirB: DirectiveB) { log.push(`DirC (dep: ${dirB.value})`); }
constructor(dirB: DirectiveB) {
log.push(`DirC (dep: ${dirB.value})`);
}
}
@Directive({selector: '[dirA]'})
class DirectiveA {
value = 'DirA';
constructor(dirC: DirectiveC) { log.push(`DirA (dep: ${dirC.value})`); }
constructor(dirC: DirectiveC) {
log.push(`DirA (dep: ${dirC.value})`);
}
}
@Directive({selector: '[dirD]'})
class DirectiveD {
value = 'DirD';
constructor(dirA: DirectiveA) { log.push(`DirD (dep: ${dirA.value})`); }
constructor(dirA: DirectiveA) {
log.push(`DirD (dep: ${dirA.value})`);
}
}
@Component({selector: 'my-comp', template: ''})
class MyComp {
constructor(dirD: DirectiveD) { log.push(`Comp (dep: ${dirD.value})`); }
constructor(dirD: DirectiveD) {
log.push(`Comp (dep: ${dirD.value})`);
}
}
@Component({template: '<my-comp dirA dirB dirC dirD></my-comp>'})
@ -291,7 +314,9 @@ describe('di', () => {
@Directive({selector: '[dirA]'})
class DirectiveA {
constructor(dirB: DirectiveB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
constructor(dirB: DirectiveB) {
log.push(`DirA (dep: DirB - ${dirB.count})`);
}
}
@Component({selector: 'my-comp', template: '<div dirA dirB></div>'})
@ -310,7 +335,6 @@ describe('di', () => {
});
describe('dependencies in parent views', () => {
@Directive({selector: '[dirA]', exportAs: 'dirA'})
class DirectiveA {
injector: Injector;
@ -385,11 +409,13 @@ describe('di', () => {
it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
@Directive({selector: '[structuralDir]'})
class StructuralDirective {
@Input() tmp !: TemplateRef<any>;
@Input() tmp!: TemplateRef<any>;
constructor(public vcr: ViewContainerRef) {}
create() { this.vcr.createEmbeddedView(this.tmp); }
create() {
this.vcr.createEmbeddedView(this.tmp);
}
}
@Component({
@ -405,7 +431,7 @@ describe('di', () => {
</div>`
})
class MyComp {
@ViewChild(StructuralDirective) structuralDir !: StructuralDirective;
@ViewChild(StructuralDirective) structuralDir!: StructuralDirective;
}
TestBed.configureTestingModule(
@ -449,8 +475,8 @@ describe('di', () => {
</div>`
})
class MyApp {
@ViewChild(HostBindingDirective) hostBindingDir !: HostBindingDirective;
@ViewChild(DirectiveA) dirA !: DirectiveA;
@ViewChild(HostBindingDirective) hostBindingDir!: HostBindingDirective;
@ViewChild(DirectiveA) dirA!: DirectiveA;
}
TestBed.configureTestingModule(
@ -501,15 +527,19 @@ describe('di', () => {
})
class MyApp {
@ViewChild('childOrigin', {read: ViewContainerRef, static: true})
childOrigin !: ViewContainerRef;
childOrigin!: ViewContainerRef;
@ViewChild('childOriginWithDirB', {read: ViewContainerRef, static: true})
childOriginWithDirB !: ViewContainerRef;
childOriginWithDirB!: ViewContainerRef;
childFactory = this.resolver.resolveComponentFactory(Child);
constructor(readonly resolver: ComponentFactoryResolver, readonly injector: Injector) {}
addChild() { return this.childOrigin.createComponent(this.childFactory); }
addChildWithDirB() { return this.childOriginWithDirB.createComponent(this.childFactory); }
addChild() {
return this.childOrigin.createComponent(this.childFactory);
}
addChildWithDirB() {
return this.childOriginWithDirB.createComponent(this.childFactory);
}
}
const fixture =
@ -604,24 +634,21 @@ describe('di', () => {
});
describe('flags', () => {
@Directive({selector: '[dirB]'})
class DirectiveB {
@Input('dirB') value = '';
}
describe('Optional', () => {
@Directive({selector: '[dirA]'})
class DirectiveA {
constructor(@Optional() public dirB: DirectiveB) {}
}
it('should not throw if dependency is @Optional (module injector)', () => {
@Component({template: '<div dirA></div>'})
class MyComp {
@ViewChild(DirectiveA) dirA !: DirectiveA;
@ViewChild(DirectiveA) dirA!: DirectiveA;
}
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp]});
@ -633,7 +660,6 @@ describe('di', () => {
});
it('should return null if @Optional dependency has @Self flag', () => {
@Directive({selector: '[dirC]'})
class DirectiveC {
constructor(@Optional() @Self() public dirB: DirectiveB) {}
@ -641,7 +667,7 @@ describe('di', () => {
@Component({template: '<div dirC></div>'})
class MyComp {
@ViewChild(DirectiveC) dirC !: DirectiveC;
@ViewChild(DirectiveC) dirC!: DirectiveC;
}
TestBed.configureTestingModule({declarations: [DirectiveC, MyComp]});
@ -653,7 +679,6 @@ describe('di', () => {
});
it('should not throw if dependency is @Optional but defined elsewhere', () => {
@Directive({selector: '[dirC]'})
class DirectiveC {
constructor(@Optional() public dirB: DirectiveB) {}
@ -661,7 +686,7 @@ describe('di', () => {
@Component({template: '<div dirB></div><div dirC></div>'})
class MyComp {
@ViewChild(DirectiveC) dirC !: DirectiveC;
@ViewChild(DirectiveC) dirC!: DirectiveC;
}
TestBed.configureTestingModule({declarations: [DirectiveB, DirectiveC, MyComp]});
@ -674,7 +699,6 @@ describe('di', () => {
});
it('should skip the current node with @SkipSelf', () => {
@Directive({selector: '[dirA]'})
class DirectiveA {
constructor(@SkipSelf() public dirB: DirectiveB) {}
@ -682,12 +706,12 @@ describe('di', () => {
@Component({selector: 'my-comp', template: '<div dirA dirB="self"></div>'})
class MyComp {
@ViewChild(DirectiveA) dirA !: DirectiveA;
@ViewChild(DirectiveA) dirA!: DirectiveA;
}
@Component({template: '<my-comp dirB="parent"></my-comp>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(MyComp) myComp!: MyComp;
}
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyComp, MyApp]});
@ -700,7 +724,6 @@ describe('di', () => {
onlyInIvy('Ivy has different error message when dependency is not found')
.it('should check only the current node with @Self', () => {
@Directive({selector: '[dirA]'})
class DirectiveA {
constructor(@Self() public dirB: DirectiveB) {}
@ -732,12 +755,12 @@ describe('di', () => {
viewProviders: [{provide: String, useValue: 'Foo'}]
})
class MyComp {
@ViewChild(DirectiveString) dirString !: DirectiveString;
@ViewChild(DirectiveString) dirString!: DirectiveString;
}
@Component({template: '<my-comp></my-comp>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(MyComp) myComp!: MyComp;
}
TestBed.configureTestingModule({declarations: [DirectiveString, MyComp, MyApp]});
@ -756,12 +779,12 @@ describe('di', () => {
@Component({selector: 'my-comp', template: '<div dirComp></div>'})
class MyComp {
@ViewChild(DirectiveComp) dirComp !: DirectiveComp;
@ViewChild(DirectiveComp) dirComp!: DirectiveComp;
}
@Component({template: '<my-comp></my-comp>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(MyComp) myComp!: MyComp;
}
TestBed.configureTestingModule({declarations: [DirectiveComp, MyComp, MyApp]});
@ -820,7 +843,7 @@ describe('di', () => {
@Component({template: '<my-comp dirB></my-comp>'})
class MyApp {
@ViewChild(MyComp) myComp !: MyComp;
@ViewChild(MyComp) myComp!: MyComp;
}
TestBed.configureTestingModule(
@ -837,8 +860,8 @@ describe('di', () => {
@Component({template: '<div dirB><div *ngIf="showing" dirA></div></div>'})
class MyApp {
showing = false;
@ViewChild(DirectiveA) dirA !: DirectiveA;
@ViewChild(DirectiveB) dirB !: DirectiveB;
@ViewChild(DirectiveA) dirA!: DirectiveA;
@ViewChild(DirectiveB) dirB!: DirectiveB;
}
TestBed.configureTestingModule({declarations: [DirectiveA, DirectiveB, MyApp]});
@ -885,7 +908,9 @@ describe('di', () => {
providers: [{provide: ControlContainer, useExisting: GroupDirective}]
})
class GroupDirective {
constructor() { controlContainers.push(this); }
constructor() {
controlContainers.push(this);
}
}
@Directive({selector: '[control]'})
@ -919,7 +944,7 @@ describe('di', () => {
const fixture = TestBed.createComponent(MyApp);
expect(fixture.nativeElement.innerHTML)
.toBe('<div group=""><my-comp><input control=""></my-comp></div>');
expect(controlContainers).toEqual([injectedControlContainer !]);
expect(controlContainers).toEqual([injectedControlContainer!]);
});
});
});
@ -958,7 +983,6 @@ describe('di', () => {
});
describe('service injection', () => {
it('should create instance even when no injector present', () => {
@Injectable({providedIn: 'root'})
class MyService {
@ -1063,7 +1087,6 @@ describe('di', () => {
expect(fixture.componentInstance.myService instanceof MyService).toBe(true);
expect(fixture.componentInstance.myOtherService instanceof MyOtherService).toBe(true);
});
});
describe('service injection with useClass', () => {
@ -1075,7 +1098,9 @@ describe('di', () => {
@Injectable({providedIn: 'root'})
class BarService {
constructor(public dep: BarServiceDep) {}
getMessage() { return 'bar'; }
getMessage() {
return 'bar';
}
}
@Injectable({providedIn: 'root'})
@ -1086,7 +1111,9 @@ describe('di', () => {
@Injectable({providedIn: 'root', useClass: BarService})
class FooService {
constructor(public dep: FooServiceDep) {}
getMessage() { return 'foo'; }
getMessage() {
return 'foo';
}
}
it('should use @Injectable useClass config when token is not provided', () => {
@ -1094,18 +1121,20 @@ describe('di', () => {
@Component({template: ''})
class App {
constructor(service: FooService) { provider = service; }
constructor(service: FooService) {
provider = service;
}
}
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(provider !.getMessage()).toBe('bar');
expect(provider!.getMessage()).toBe('bar');
// ViewEngine incorrectly uses the original class DI config, instead of the one from useClass.
if (ivyEnabled) {
expect(provider !.dep.name).toBe('BarServiceDep');
expect(provider!.dep.name).toBe('BarServiceDep');
}
});
@ -1115,7 +1144,9 @@ describe('di', () => {
@Component({template: ''})
class App {
constructor(service: FooService) { provider = service; }
constructor(service: FooService) {
provider = service;
}
}
TestBed.configureTestingModule(
@ -1123,7 +1154,7 @@ describe('di', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(provider !.getMessage()).toBe('foo');
expect(provider!.getMessage()).toBe('foo');
});
@ -1144,13 +1175,13 @@ describe('di', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(directProvider !.getMessage()).toBe('bar');
expect(overriddenProvider !.getMessage()).toBe('foo');
expect(directProvider!.getMessage()).toBe('bar');
expect(overriddenProvider!.getMessage()).toBe('foo');
// ViewEngine incorrectly uses the original class DI config, instead of the one from useClass.
if (ivyEnabled) {
expect(directProvider !.dep.name).toBe('BarServiceDep');
expect(overriddenProvider !.dep.name).toBe('FooServiceDep');
expect(directProvider!.dep.name).toBe('BarServiceDep');
expect(overriddenProvider!.dep.name).toBe('FooServiceDep');
}
});
@ -1160,20 +1191,21 @@ describe('di', () => {
@Component({template: ''})
class App {
constructor(service: FooService) { provider = service; }
constructor(service: FooService) {
provider = service;
}
}
TestBed.configureTestingModule({declarations: [App], providers: [FooService]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(provider !.getMessage()).toBe('foo');
expect(provider !.dep.name).toBe('FooServiceDep');
expect(provider!.getMessage()).toBe('foo');
expect(provider!.dep.name).toBe('FooServiceDep');
});
});
describe('inject', () => {
it('should inject from parent view', () => {
@Directive({selector: '[parentDir]'})
class ParentDirective {
@ -1182,7 +1214,9 @@ describe('di', () => {
@Directive({selector: '[childDir]', exportAs: 'childDir'})
class ChildDirective {
value: string;
constructor(public parent: ParentDirective) { this.value = parent.constructor.name; }
constructor(public parent: ParentDirective) {
this.value = parent.constructor.name;
}
}
@Directive({selector: '[child2Dir]', exportAs: 'child2Dir'})
@ -1214,9 +1248,7 @@ describe('di', () => {
});
describe('Special tokens', () => {
describe('Injector', () => {
it('should inject the injector', () => {
@Directive({selector: '[injectorDir]'})
class InjectorDir {
@ -1230,8 +1262,8 @@ describe('di', () => {
@Component({template: '<div injectorDir otherInjectorDir></div>'})
class MyComp {
@ViewChild(InjectorDir) injectorDir !: InjectorDir;
@ViewChild(OtherInjectorDir) otherInjectorDir !: OtherInjectorDir;
@ViewChild(InjectorDir) injectorDir!: InjectorDir;
@ViewChild(OtherInjectorDir) otherInjectorDir!: OtherInjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, OtherInjectorDir, MyComp]});
@ -1256,7 +1288,7 @@ describe('di', () => {
@Component({template: '<div injectorDir></div>'})
class MyComp {
@ViewChild(InjectorDir) injectorDir !: InjectorDir;
@ViewChild(InjectorDir) injectorDir!: InjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]});
@ -1273,7 +1305,6 @@ describe('di', () => {
});
describe('ElementRef', () => {
it('should create directive with ElementRef dependencies', () => {
@Directive({selector: '[dir]'})
class MyDir {
@ -1293,8 +1324,8 @@ describe('di', () => {
@Component({template: '<div dir otherDir></div>'})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
@ -1325,7 +1356,7 @@ describe('di', () => {
@Component({template: '<ng-template dir></ng-template>'})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyDir) directive!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1346,7 +1377,9 @@ describe('di', () => {
constructor(protected zone: NgZone) {
this.subject = new BehaviorSubject<any>(1);
// trigger change detection
zone.run(() => { this.subject.next(2); });
zone.run(() => {
this.subject.next(2);
});
}
}
@ -1360,7 +1393,7 @@ describe('di', () => {
template: `<div id="test-id" dir></div>`,
})
class ChildComp {
@ViewChild(DirectiveA) directive !: DirectiveA;
@ViewChild(DirectiveA) directive!: DirectiveA;
}
@Component({
@ -1368,7 +1401,7 @@ describe('di', () => {
template: '...',
})
class RootComp {
public childCompRef !: ComponentRef<ChildComp>;
public childCompRef!: ComponentRef<ChildComp>;
constructor(
public factoryResolver: ComponentFactoryResolver, public vcr: ViewContainerRef) {}
@ -1404,7 +1437,6 @@ describe('di', () => {
});
describe('TemplateRef', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDir {
value: string;
@ -1426,8 +1458,8 @@ describe('di', () => {
template: '<ng-template dir otherDir #dir="dir" #otherDir="otherDir"></ng-template>'
})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
@ -1470,7 +1502,7 @@ describe('di', () => {
}
@Component({template: '<div optionalDir></div>'})
class MyComp {
@ViewChild(OptionalDir) directive !: OptionalDir;
@ViewChild(OptionalDir) directive!: OptionalDir;
}
TestBed.configureTestingModule({declarations: [OptionalDir, MyComp]});
@ -1499,8 +1531,8 @@ describe('di', () => {
}
@Component({template: '<div dir otherDir #dir="dir" #otherDir="otherDir"></div>'})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
@ -1524,12 +1556,13 @@ describe('di', () => {
template: `<ng-template #tmpl>Test</ng-template>`,
})
class Root {
@ViewChild(TemplateRef, {static: true})
tmpl !: TemplateRef<any>;
@ViewChild(TemplateRef, {static: true}) tmpl!: TemplateRef<any>;
constructor(public vcr: ViewContainerRef, public vcr2: ViewContainerRef) {}
ngOnInit(): void { this.vcr.createEmbeddedView(this.tmpl); }
ngOnInit(): void {
this.vcr.createEmbeddedView(this.tmpl);
}
}
TestBed.configureTestingModule({
@ -1551,11 +1584,12 @@ describe('di', () => {
});
describe('ChangeDetectorRef', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDir {
value: string;
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
constructor(public cdr: ChangeDetectorRef) {
this.value = (cdr.constructor as any).name;
}
}
@Directive({selector: '[otherDir]', exportAs: 'otherDir'})
class MyOtherDir {
@ -1571,9 +1605,13 @@ describe('di', () => {
@Pipe({name: 'pipe'})
class MyPipe implements PipeTransform {
constructor(public cdr: ChangeDetectorRef) { pipeInstance = this; }
constructor(public cdr: ChangeDetectorRef) {
pipeInstance = this;
}
transform(value: any): any { return value; }
transform(value: any): any {
return value;
}
}
@Component({
@ -1589,29 +1627,29 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [MyApp, MyPipe], imports: [CommonModule]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
expect((pipeInstance !.cdr as ViewRef<MyApp>).context).toBe(fixture.componentInstance);
expect((pipeInstance!.cdr as ViewRef<MyApp>).context).toBe(fixture.componentInstance);
});
it('should inject current component ChangeDetectorRef into directives on the same node as components',
() => {
@Component({selector: 'my-app', template: '<my-comp dir otherDir #dir="dir"></my-comp>'})
class MyApp {
@ViewChild(MyComp) component !: MyComp;
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyComp) component!: MyComp;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
const app = fixture.componentInstance;
const comp = fixture.componentInstance.component;
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
expect((comp!.cdr as ViewRef<MyComp>).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(app.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(app.directive !.cdr).not.toBe(comp !.cdr);
expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr);
expect(app.directive!.cdr).not.toBe(comp!.cdr);
expect(app.directive!.cdr).not.toBe(app.otherDirective!.cdr);
});
it('should inject host component ChangeDetectorRef into directives on normal elements',
@ -1619,20 +1657,20 @@ describe('di', () => {
@Component({selector: 'my-comp', template: '<div dir otherDir #dir="dir"></div>'})
class MyComp {
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
expect((comp!.cdr as ViewRef<MyComp>).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
expect(comp.directive!.cdr).not.toBe(comp.cdr);
expect(comp.directive!.cdr).not.toBe(comp.otherDirective!.cdr);
});
it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren',
@ -1646,22 +1684,22 @@ describe('di', () => {
})
class MyApp {
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyComp) component !: MyComp;
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyComp) component!: MyComp;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
const app = fixture.componentInstance;
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
expect((app!.cdr as ViewRef<MyApp>).context).toBe(app);
const comp = fixture.componentInstance.component;
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(app.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(app.directive !.cdr).not.toBe(comp.cdr);
expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr);
expect(app.directive!.cdr).not.toBe(comp.cdr);
expect(app.directive!.cdr).not.toBe(app.otherDirective!.cdr);
});
it('should inject host component ChangeDetectorRef into directives in embedded views', () => {
@ -1674,21 +1712,21 @@ describe('di', () => {
class MyComp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
expect((comp!.cdr as ViewRef<MyComp>).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
expect(comp.directive!.cdr).not.toBe(comp.cdr);
expect(comp.directive!.cdr).not.toBe(comp.otherDirective!.cdr);
});
it('should inject host component ChangeDetectorRef into directives on containers', () => {
@ -1697,21 +1735,21 @@ describe('di', () => {
class MyComp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
@ViewChild(MyDir) directive!: MyDir;
@ViewChild(MyOtherDir) otherDirective!: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
expect((comp!.cdr as ViewRef<MyComp>).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
expect(comp.directive!.cdr).not.toBe(comp.cdr);
expect(comp.directive!.cdr).not.toBe(comp.otherDirective!.cdr);
});
it('should inject host component ChangeDetectorRef into directives on ng-container', () => {
@ -1719,7 +1757,9 @@ describe('di', () => {
@Directive({selector: '[getCDR]'})
class MyDirective {
constructor(public cdr: ChangeDetectorRef) { dirInstance = this; }
constructor(public cdr: ChangeDetectorRef) {
dirInstance = this;
}
}
@Component({
@ -1733,7 +1773,7 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [MyApp, MyDirective]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
expect((dirInstance !.cdr as ViewRef<MyApp>).context).toBe(fixture.componentInstance);
expect((dirInstance!.cdr as ViewRef<MyApp>).context).toBe(fixture.componentInstance);
});
});
});
@ -1747,7 +1787,7 @@ describe('di', () => {
@Component({template: '<div injectorDir></div>'})
class MyComp {
@ViewChild(InjectorDir) injectorDirInstance !: InjectorDir;
@ViewChild(InjectorDir) injectorDirInstance!: InjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]});
@ -1840,7 +1880,7 @@ describe('di', () => {
providers: [{provide: LOCALE_ID, useValue: 'en-GB'}]
})
class MyComp {
@ViewChild(MyDir) myDir !: MyDir;
@ViewChild(MyDir) myDir!: MyDir;
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
@ -1851,7 +1891,6 @@ describe('di', () => {
});
describe('@Attribute', () => {
it('should inject attributes', () => {
@Directive({selector: '[dir]'})
class MyDir {
@ -1862,7 +1901,7 @@ describe('di', () => {
@Component({template: '<div dir exist="existValue" other="ignore"></div>'})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1886,7 +1925,7 @@ describe('di', () => {
@Component(
{template: '<ng-template dir="initial" exist="existValue" other="ignore"></ng-template>'})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1911,7 +1950,7 @@ describe('di', () => {
template: '<ng-container dir="initial" exist="existValue" other="ignore"></ng-container>'
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1938,7 +1977,7 @@ describe('di', () => {
'<div dir style="margin: 1px; color: red;" class="hello there" other-attr="value"></div>'
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1966,7 +2005,7 @@ describe('di', () => {
template: '<div dir exist="existValue" svg:exist="testExistValue" other="otherValue"></div>'
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -1983,7 +2022,7 @@ describe('di', () => {
it('should not inject attributes representing bindings and outputs', () => {
@Directive({selector: '[dir]'})
class MyDir {
@Input() binding !: string;
@Input() binding!: string;
@Output() output = new EventEmitter();
constructor(
@Attribute('exist') public exist: string,
@ -1997,7 +2036,7 @@ describe('di', () => {
'<div dir exist="existValue" [binding]="bindingValue" (output)="outputValue" other="otherValue" ignore="ignoreValue"></div>'
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
@ViewChild(MyDir) directiveInstance!: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -2016,13 +2055,17 @@ describe('di', () => {
it('should support dependencies in Pipes used inside ICUs', () => {
@Injectable()
class MyService {
transform(value: string): string { return `${value} (transformed)`; }
transform(value: string): string {
return `${value} (transformed)`;
}
}
@Pipe({name: 'somePipe'})
class MyPipe {
constructor(private service: MyService) {}
transform(value: any): any { return this.service.transform(value); }
transform(value: any): any {
return this.service.transform(value);
}
}
@Component({
@ -2051,13 +2094,17 @@ describe('di', () => {
it('should support dependencies in Pipes used inside i18n blocks', () => {
@Injectable()
class MyService {
transform(value: string): string { return `${value} (transformed)`; }
transform(value: string): string {
return `${value} (transformed)`;
}
}
@Pipe({name: 'somePipe'})
class MyPipe {
constructor(private service: MyService) {}
transform(value: any): any { return this.service.transform(value); }
transform(value: any): any {
return this.service.transform(value);
}
}
@Component({
@ -2071,10 +2118,12 @@ describe('di', () => {
class MyComp {
count = '2';
@ViewChild('target', {read: ViewContainerRef}) target !: ViewContainerRef;
@ViewChild('source', {read: TemplateRef}) source !: TemplateRef<any>;
@ViewChild('target', {read: ViewContainerRef}) target!: ViewContainerRef;
@ViewChild('source', {read: TemplateRef}) source!: TemplateRef<any>;
create() { this.target.createEmbeddedView(this.source); }
create() {
this.target.createEmbeddedView(this.source);
}
}
TestBed.configureTestingModule({
@ -2109,8 +2158,8 @@ describe('di', () => {
@Component({template: `<div dirA> <child></child> </div>`})
class App {
@ViewChild(DirA) dirA !: DirA;
@ViewChild(Child) child !: Child;
@ViewChild(DirA) dirA!: DirA;
@ViewChild(Child) child!: Child;
}
const fixture = TestBed.configureTestingModule({declarations: [DirA, DirB, App, Child]})

View File

@ -13,9 +13,7 @@ import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
describe('directives', () => {
describe('matching', () => {
@Directive({selector: 'ng-template[test]'})
class TestDirective {
constructor(public templateRef: TemplateRef<any>) {}
@ -241,7 +239,9 @@ describe('directives', () => {
@Directive({selector: '[dir]'})
class MyDir {
ngOnInit() { calls.push('MyDir.ngOnInit'); }
ngOnInit() {
calls.push('MyDir.ngOnInit');
}
}
@Component({
@ -266,7 +266,9 @@ describe('directives', () => {
@Directive({selector: 'svg[dir]'})
class MyDir {
constructor(private el: ElementRef) {}
ngOnInit() { calls.push(`MyDir.ngOnInit: ${this.el.nativeElement.tagName}`); }
ngOnInit() {
calls.push(`MyDir.ngOnInit: ${this.el.nativeElement.tagName}`);
}
}
@Component({
@ -289,7 +291,9 @@ describe('directives', () => {
@Directive({selector: 'text[dir]'})
class MyDir {
constructor(private el: ElementRef) {}
ngOnInit() { calls.push(`MyDir.ngOnInit: ${this.el.nativeElement.tagName}`); }
ngOnInit() {
calls.push(`MyDir.ngOnInit: ${this.el.nativeElement.tagName}`);
}
}
@Component({
@ -311,13 +315,13 @@ describe('directives', () => {
@Directive({selector: '[test]'})
class MyDir {
constructor() { logs.push('MyDir.contructor'); }
constructor() {
logs.push('MyDir.contructor');
}
@Input('test')
myInput = '';
@Input('test') myInput = '';
@Input('disabled')
myInput2 = '';
@Input('disabled') myInput2 = '';
}
@Component({
@ -338,7 +342,6 @@ describe('directives', () => {
expect(logs).toEqual(['MyDir.contructor']);
});
});
describe('inputs', () => {
@ -346,7 +349,9 @@ describe('directives', () => {
let dirInstance: WithInput;
@Directive({selector: '[dir]'})
class WithInput {
constructor() { dirInstance = this; }
constructor() {
dirInstance = this;
}
@Input() dir: string = '';
}
@ -362,14 +367,16 @@ describe('directives', () => {
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
expect(dirInstance !.dir).toBe('Hello');
expect(dirInstance!.dir).toBe('Hello');
});
it('should allow directive inputs (as an interpolated prop) on <ng-template>', () => {
let dirInstance: WithInput;
@Directive({selector: '[dir]'})
class WithInput {
constructor() { dirInstance = this; }
constructor() {
dirInstance = this;
}
@Input() dir: string = '';
}
@ -385,7 +392,7 @@ describe('directives', () => {
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
expect(dirInstance !.dir).toBe('Hello');
expect(dirInstance!.dir).toBe('Hello');
});
it('should allow directive inputs (as an interpolated prop) on <ng-template> with structural directives',
@ -393,7 +400,9 @@ describe('directives', () => {
let dirInstance: WithInput;
@Directive({selector: '[dir]'})
class WithInput {
constructor() { dirInstance = this; }
constructor() {
dirInstance = this;
}
@Input() dir: string = '';
}
@ -409,7 +418,7 @@ describe('directives', () => {
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
expect(dirInstance !.dir).toBe('Hello');
expect(dirInstance!.dir).toBe('Hello');
});
});
@ -433,7 +442,7 @@ describe('directives', () => {
expect(fixture.componentInstance.testDir).toBeTruthy();
expect(fixture.componentInstance.value).toBe(false);
fixture.componentInstance.testDir !.out.emit();
fixture.componentInstance.testDir!.out.emit();
fixture.detectChanges();
expect(fixture.componentInstance.value).toBe(true);
});
@ -459,7 +468,6 @@ describe('directives', () => {
fixture.detectChanges();
expect(fixture.componentInstance.value).toBeTruthy();
});
});
describe('attribute shadowing behaviors', () => {
@ -471,8 +479,7 @@ describe('directives', () => {
selector: '[dir-with-title]',
})
class DirWithTitle {
@Input()
title = '';
@Input() title = '';
}
it('should set both the div attribute and the directive input for `title="value"`', () => {
@ -711,8 +718,12 @@ describe('directives', () => {
const log: string[] = [];
@Directive({selector: '[dir]'})
class DirectiveA {
constructor() { log.push('DirectiveA.constructor'); }
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
constructor() {
log.push('DirectiveA.constructor');
}
ngOnInit() {
log.push('DirectiveA.ngOnInit');
}
}
@NgModule({
@ -724,8 +735,12 @@ describe('directives', () => {
@Directive({selector: '[dir]'})
class DirectiveB {
constructor() { log.push('DirectiveB.constructor'); }
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
constructor() {
log.push('DirectiveB.constructor');
}
ngOnInit() {
log.push('DirectiveB.ngOnInit');
}
}
@Component({
@ -752,8 +767,12 @@ describe('directives', () => {
const log: string[] = [];
@Directive({selector: '[dir]'})
class DirectiveA {
constructor() { log.push('DirectiveA.constructor'); }
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
constructor() {
log.push('DirectiveA.constructor');
}
ngOnInit() {
log.push('DirectiveA.ngOnInit');
}
}
@NgModule({
@ -765,8 +784,12 @@ describe('directives', () => {
@Directive({selector: '[dir]'})
class DirectiveB {
constructor() { log.push('DirectiveB.constructor'); }
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
constructor() {
log.push('DirectiveB.constructor');
}
ngOnInit() {
log.push('DirectiveB.ngOnInit');
}
}
@NgModule({
@ -795,6 +818,5 @@ describe('directives', () => {
'DirectiveB.ngOnInit'
]);
});
});
});

View File

@ -48,12 +48,16 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
@Component(
{selector: 'child', template: '<p></p>', providers: [{provide: String, useValue: 'Child'}]})
class Child {
constructor() { childComponent.push(this); }
constructor() {
childComponent.push(this);
}
}
@Directive({selector: '[dirA]', exportAs: 'dirA'})
class DirectiveA {
constructor() { dirA.push(this); }
constructor() {
dirA.push(this);
}
}
@Component({
@ -69,9 +73,13 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
})
class MyApp {
text: string = 'INIT';
constructor() { myApp = this; }
constructor() {
myApp = this;
}
log(event: any) { log.push(event); }
log(event: any) {
log.push(event);
}
}
describe('getComponent', () => {
@ -112,7 +120,7 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
});
it('should return context from element', () => {
expect(getContext<MyApp>(child[0])).toEqual(myApp);
expect(getContext<{$implicit: boolean}>(child[2]) !.$implicit).toEqual(true);
expect(getContext<{$implicit: boolean}>(child[2])!.$implicit).toEqual(true);
expect(getContext<Child>(p[0])).toEqual(childComponent[0]);
});
});
@ -264,7 +272,7 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
it('should work on templates', () => {
const templateComment = Array.from((fixture.nativeElement as HTMLElement).childNodes)
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE) !;
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE)!;
const lContext = loadLContext(templateComment);
expect(lContext).toBeDefined();
expect(lContext.native as any).toBe(templateComment);
@ -274,7 +282,7 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
const ngContainerComment = Array.from((fixture.nativeElement as HTMLElement).childNodes)
.find(
(node: ChildNode) => node.nodeType === Node.COMMENT_NODE &&
node.textContent === `ng-container`) !;
node.textContent === `ng-container`)!;
const lContext = loadLContext(ngContainerComment);
expect(lContext).toBeDefined();
expect(lContext.native as any).toBe(ngContainerComment);
@ -285,7 +293,6 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => {
onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () => {
describe('getRootComponents()', () => {
it('should return a list of the root components of the application from an element', () => {
@Component({selector: 'inner-comp', template: '<div></div>'})
class InnerComp {
}
@ -299,13 +306,13 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
fixture.detectChanges();
const hostElm = fixture.nativeElement;
const innerElm = hostElm.querySelector('inner-comp') !;
const divElm = hostElm.querySelector('div') !;
const innerElm = hostElm.querySelector('inner-comp')!;
const divElm = hostElm.querySelector('div')!;
const component = fixture.componentInstance;
expect(getRootComponents(hostElm) !).toEqual([component]);
expect(getRootComponents(innerElm) !).toEqual([component]);
expect(getRootComponents(divElm) !).toEqual([component]);
expect(getRootComponents(hostElm)!).toEqual([component]);
expect(getRootComponents(innerElm)!).toEqual([component]);
expect(getRootComponents(divElm)!).toEqual([component]);
});
});
@ -331,9 +338,9 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
`
})
class Comp {
@ViewChild(MyDir1) myDir1Instance !: MyDir1;
@ViewChild(MyDir2) myDir2Instance !: MyDir2;
@ViewChild(MyDir3) myDir3Instance !: MyDir3;
@ViewChild(MyDir1) myDir1Instance!: MyDir1;
@ViewChild(MyDir2) myDir2Instance!: MyDir2;
@ViewChild(MyDir3) myDir3Instance!: MyDir3;
}
TestBed.configureTestingModule({declarations: [Comp, MyDir1, MyDir2, MyDir3]});
@ -345,19 +352,17 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
const elm1 = elements[0];
const elm1Dirs = getDirectives(elm1);
expect(elm1Dirs).toContain(fixture.componentInstance.myDir1Instance !);
expect(elm1Dirs).toContain(fixture.componentInstance.myDir2Instance !);
expect(elm1Dirs).toContain(fixture.componentInstance.myDir1Instance!);
expect(elm1Dirs).toContain(fixture.componentInstance.myDir2Instance!);
const elm2 = elements[1];
const elm2Dirs = getDirectives(elm2);
expect(elm2Dirs).toContain(fixture.componentInstance.myDir3Instance !);
expect(elm2Dirs).toContain(fixture.componentInstance.myDir3Instance!);
});
});
describe('getInjector', () => {
it('should return an injector that can return directive instances', () => {
@Component({template: ''})
class Comp {
}
@ -386,7 +391,6 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
describe('getLocalRefs', () => {
it('should return a map of local refs for an element', () => {
@Directive({selector: '[myDir]', exportAs: 'myDir'})
class MyDir {
}
@ -399,7 +403,7 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
const fixture = TestBed.createComponent(Comp);
fixture.detectChanges();
const divEl = fixture.nativeElement.querySelector('div') !;
const divEl = fixture.nativeElement.querySelector('div')!;
const localRefs = getLocalRefs(divEl);
expect(localRefs.elRef.tagName.toLowerCase()).toBe('div');
@ -416,7 +420,7 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
const fixture = TestBed.createComponent(Comp);
fixture.detectChanges();
const divEl = fixture.nativeElement.querySelector('div') !;
const divEl = fixture.nativeElement.querySelector('div')!;
const localRefs = getLocalRefs(divEl);
expect(localRefs.elRef.tagName.toLowerCase()).toBe('div');
@ -439,11 +443,11 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
const fixture = TestBed.createComponent(Comp);
fixture.detectChanges();
const parent = fixture.nativeElement.querySelector('.parent') !;
const child = fixture.nativeElement.querySelector('.child') !;
const parent = fixture.nativeElement.querySelector('.parent')!;
const child = fixture.nativeElement.querySelector('.child')!;
const parentDebug = getDebugNode(parent) !;
const childDebug = getDebugNode(child) !;
const parentDebug = getDebugNode(parent)!;
const childDebug = getDebugNode(child)!;
expect(parentDebug.native).toBe(parent);
expect(childDebug.native).toBe(child);
@ -472,8 +476,8 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
const fixture = TestBed.createComponent(Comp);
fixture.detectChanges();
const child = fixture.nativeElement.querySelector('child-comp') !;
const childDebug = getDebugNode(child) !;
const child = fixture.nativeElement.querySelector('child-comp')!;
const childDebug = getDebugNode(child)!;
expect(childDebug.native).toBe(child);
expect(getElementStyles(child)).toEqual({

View File

@ -10,7 +10,6 @@ import {Component, Input} from '@angular/core';
import {TestBed} from '@angular/core/testing';
describe('embedded views', () => {
it('should correctly resolve the implicit receiver in expressions', () => {
const items: string[] = [];
@ -27,7 +26,9 @@ describe('embedded views', () => {
})
class TestCmp {
item: string = 'CmpItem';
addItem() { items.push(this.item); }
addItem() {
items.push(this.item);
}
}
TestBed.configureTestingModule({declarations: [ChildCmp, TestCmp]});
@ -36,8 +37,8 @@ describe('embedded views', () => {
const childCmp: ChildCmp = fixture.debugElement.children[0].componentInstance;
childCmp.addItemFn !();
childCmp.addItemFn !();
childCmp.addItemFn!();
childCmp.addItemFn!();
expect(items).toEqual(['CmpItem', 'CmpItem']);
});
@ -71,5 +72,4 @@ describe('embedded views', () => {
expect(fixture.nativeElement.textContent).toBe('HelloHello');
});
});

View File

@ -247,7 +247,13 @@ class DirWithCompInput {
class DirToReferenceWithPreOrderHooks implements OnInit, OnChanges, DoCheck {
@Input() in : any = null;
name = 'Drew';
ngOnChanges(changes: SimpleChanges) { this.name += '!'; }
ngOnInit() { this.name += '?'; }
ngDoCheck() { this.name += '@'; }
ngOnChanges(changes: SimpleChanges) {
this.name += '!';
}
ngOnInit() {
this.name += '?';
}
ngDoCheck() {
this.name += '@';
}
}

View File

@ -121,10 +121,9 @@ describe('host bindings', () => {
class ParentCmp {
private _prop = '';
@ViewChild('template', {read: ViewContainerRef})
vcr: ViewContainerRef = null !;
@ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!;
private child: ComponentRef<ChildCmp> = null !;
private child: ComponentRef<ChildCmp> = null!;
@Input()
set prop(value: string) {
@ -137,10 +136,11 @@ describe('host bindings', () => {
}
}
get prop() { return this._prop; }
get prop() {
return this._prop;
}
@Input()
prop2 = 0;
@Input() prop2 = 0;
ngAfterViewInit() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ChildCmp);
@ -212,7 +212,6 @@ describe('host bindings', () => {
onlyInIvy('[style.prop] and [class.name] prioritization is a new feature')
.it('should prioritize styling present in the order of directive hostBinding evaluation, but consider sub-classed directive styling to be the most important',
() => {
@Component({template: '<div child-dir sibling-dir></div>'})
class MyApp {
}
@ -220,37 +219,55 @@ describe('host bindings', () => {
@Directive({selector: '[parent-dir]'})
class ParentDir {
@HostBinding('style.width')
get width1() { return '100px'; }
get width1() {
return '100px';
}
@HostBinding('style.height')
get height1() { return '100px'; }
get height1() {
return '100px';
}
@HostBinding('style.color')
get color1() { return 'red'; }
get color1() {
return 'red';
}
}
@Directive({selector: '[child-dir]'})
class ChildDir extends ParentDir {
@HostBinding('style.width')
get width2() { return '200px'; }
get width2() {
return '200px';
}
@HostBinding('style.height')
get height2() { return '200px'; }
get height2() {
return '200px';
}
}
@Directive({selector: '[sibling-dir]'})
class SiblingDir {
@HostBinding('style.width')
get width3() { return '300px'; }
get width3() {
return '300px';
}
@HostBinding('style.height')
get height3() { return '300px'; }
get height3() {
return '300px';
}
@HostBinding('style.opacity')
get opacity3() { return '0.5'; }
get opacity3() {
return '0.5';
}
@HostBinding('style.color')
get color1() { return 'blue'; }
get color1() {
return 'blue';
}
}
TestBed.configureTestingModule(
@ -295,25 +312,22 @@ describe('host bindings', () => {
fixture.detectChanges();
}).not.toThrow();
});
});
@Directive({selector: '[hostBindingDir]'})
class HostBindingDir {
@HostBinding()
id = 'foo';
@HostBinding() id = 'foo';
}
it('should support host bindings in directives', () => {
@Directive({selector: '[dir]'})
class Dir {
@HostBinding('className')
klass = 'foo';
@HostBinding('className') klass = 'foo';
}
@Component({template: '<span dir></span>'})
class App {
@ViewChild(Dir) directiveInstance !: Dir;
@ViewChild(Dir) directiveInstance!: Dir;
}
TestBed.configureTestingModule({declarations: [App, Dir]});
@ -333,8 +347,7 @@ describe('host bindings', () => {
it('should support host bindings on root component', () => {
@Component({template: ''})
class HostBindingComp {
@HostBinding()
title = 'my-title';
@HostBinding() title = 'my-title';
}
TestBed.configureTestingModule({declarations: [HostBindingComp]});
@ -365,8 +378,7 @@ describe('host bindings', () => {
class App {
constructor(public serviceOne: ServiceOne, public serviceTwo: ServiceTwo) {}
@HostBinding()
title = 'my-title';
@HostBinding() title = 'my-title';
}
TestBed.configureTestingModule({declarations: [App]});
@ -390,8 +402,7 @@ describe('host bindings', () => {
@Component({selector: 'host-title-comp', template: ''})
class HostTitleComp {
@HostBinding()
title = 'my-title';
@HostBinding() title = 'my-title';
}
@Component({
@ -402,7 +413,7 @@ describe('host bindings', () => {
`
})
class App {
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
@ViewChild(HostBindingDir) hostBindingDir!: HostBindingDir;
}
TestBed.configureTestingModule({declarations: [App, SomeDir, HostTitleComp, HostBindingDir]});
@ -415,7 +426,7 @@ describe('host bindings', () => {
expect(hostBindingDiv.id).toEqual('foo');
expect(hostTitleComp.title).toEqual('my-title');
fixture.componentInstance.hostBindingDir !.id = 'bar';
fixture.componentInstance.hostBindingDir!.id = 'bar';
fixture.detectChanges();
expect(hostBindingDiv.id).toEqual('bar');
});
@ -423,8 +434,7 @@ describe('host bindings', () => {
it('should support consecutive components with host bindings', () => {
@Component({selector: 'host-binding-comp', template: ''})
class HostBindingComp {
@HostBinding()
id = 'blue';
@HostBinding() id = 'blue';
}
@Component({
@ -434,7 +444,7 @@ describe('host bindings', () => {
`
})
class App {
@ViewChildren(HostBindingComp) hostBindingComp !: QueryList<HostBindingComp>;
@ViewChildren(HostBindingComp) hostBindingComp!: QueryList<HostBindingComp>;
}
TestBed.configureTestingModule({declarations: [App, HostBindingComp]});
@ -470,7 +480,7 @@ describe('host bindings', () => {
@Component({template: '<div someDir hostBindingDir></div>'})
class App {
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
@ViewChild(HostBindingDir) hostBindingDir!: HostBindingDir;
}
TestBed.configureTestingModule({declarations: [App, SomeDir, HostBindingDir]});
@ -480,7 +490,7 @@ describe('host bindings', () => {
const hostBindingDiv = fixture.nativeElement.querySelector('div') as HTMLElement;
expect(hostBindingDiv.id).toEqual('foo');
fixture.componentInstance.hostBindingDir !.id = 'bar';
fixture.componentInstance.hostBindingDir!.id = 'bar';
fixture.detectChanges();
expect(hostBindingDiv.id).toEqual('bar');
});
@ -490,18 +500,23 @@ describe('host bindings', () => {
it('should support host bindings that rely on values from init hooks', () => {
@Component({template: '', selector: 'init-hook-comp'})
class InitHookComp implements OnInit, OnChanges, DoCheck {
@Input()
inputValue = '';
@Input() inputValue = '';
changesValue = '';
initValue = '';
checkValue = '';
ngOnChanges() { this.changesValue = 'changes'; }
ngOnChanges() {
this.changesValue = 'changes';
}
ngOnInit() { this.initValue = 'init'; }
ngOnInit() {
this.initValue = 'init';
}
ngDoCheck() { this.checkValue = 'check'; }
ngDoCheck() {
this.checkValue = 'check';
}
@HostBinding('title')
get value() {
@ -529,16 +544,14 @@ describe('host bindings', () => {
it('should support host bindings with the same name as inputs', () => {
@Directive({selector: '[hostBindingDir]'})
class HostBindingInputDir {
@Input()
disabled = false;
@Input() disabled = false;
@HostBinding('disabled')
hostDisabled = false;
@HostBinding('disabled') hostDisabled = false;
}
@Component({template: '<input hostBindingDir [disabled]="isDisabled">'})
class App {
@ViewChild(HostBindingInputDir) hostBindingInputDir !: HostBindingInputDir;
@ViewChild(HostBindingInputDir) hostBindingInputDir!: HostBindingInputDir;
isDisabled = true;
}
@ -611,14 +624,12 @@ describe('host bindings', () => {
it('should support component with host bindings and array literals', () => {
@Component({selector: 'host-binding-comp', template: ''})
class HostBindingComp {
@HostBinding()
id = 'my-id';
@HostBinding() id = 'my-id';
}
@Component({selector: 'name-comp', template: ''})
class NameComp {
@Input()
names !: string[];
@Input() names!: string[];
}
@Component({
@ -628,7 +639,7 @@ describe('host bindings', () => {
`
})
class App {
@ViewChild(NameComp) nameComp !: NameComp;
@ViewChild(NameComp) nameComp!: NameComp;
name = '';
}
@ -661,8 +672,7 @@ describe('host bindings', () => {
it('should support host bindings that contain array literals', () => {
@Component({selector: 'name-comp', template: ''})
class NameComp {
@Input()
names !: string[];
@Input() names!: string[];
}
@Component({
@ -684,8 +694,8 @@ describe('host bindings', () => {
`
})
class App {
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
@ViewChild(NameComp) nameComp !: NameComp;
@ViewChild(HostBindingComp) hostBindingComp!: HostBindingComp;
@ViewChild(NameComp) nameComp!: NameComp;
name = '';
otherName = '';
}
@ -703,11 +713,11 @@ describe('host bindings', () => {
expect(hostBindingEl.id).toBe('red,blue');
expect(hostBindingEl.dir).toBe('ltr');
expect(hostBindingEl.title).toBe('my title,other title');
expect(nameComp !.names).toEqual(['Frank', 'Nancy', 'Joe']);
expect(nameComp!.names).toEqual(['Frank', 'Nancy', 'Joe']);
const firstArray = nameComp !.names;
const firstArray = nameComp!.names;
fixture.detectChanges();
expect(firstArray).toBe(nameComp !.names);
expect(firstArray).toBe(nameComp!.names);
hostBindingComp.id = 'green';
hostBindingComp.dir = 'rtl';
@ -729,7 +739,9 @@ describe('host bindings', () => {
@Directive({selector: '[hostListenerDir]'})
class HostListenerDir {
@HostListener('click')
onClick() { events.push('click!'); }
onClick() {
events.push('click!');
}
}
@Component({template: '<button hostListenerDir hostDir>Click</button>'})
@ -740,7 +752,7 @@ describe('host bindings', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const button = fixture.nativeElement.querySelector('button') !;
const button = fixture.nativeElement.querySelector('button')!;
button.click();
expect(events).toEqual(['click!']);
expect(button.title).toEqual('my title,other title');
@ -759,8 +771,8 @@ describe('host bindings', () => {
@Component({template: '<host-binding-comp hostDir></host-binding-comp>'})
class App {
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
@ViewChild(HostBindingComp) hostBindingComp!: HostBindingComp;
@ViewChild(HostBindingDir) hostBindingDir!: HostBindingDir;
}
TestBed.configureTestingModule({declarations: [App, HostBindingComp, HostBindingDir]});
@ -798,7 +810,7 @@ describe('host bindings', () => {
@Component({template: `<host-binding-comp></host-binding-comp>{{ name }}`})
class App {
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
@ViewChild(HostBindingComp) hostBindingComp!: HostBindingComp;
name = '';
}
@ -872,8 +884,8 @@ describe('host bindings', () => {
`
})
class App {
@ViewChild(SubDirective) subDir !: SubDirective;
@ViewChild(SuperDirective) superDir !: SuperDirective;
@ViewChild(SubDirective) subDir!: SubDirective;
@ViewChild(SuperDirective) superDir!: SuperDirective;
}
TestBed.configureTestingModule({declarations: [App, SuperDirective, SubDirective]});
@ -927,8 +939,7 @@ describe('host bindings', () => {
host: {'[id]': 'foos.length'}
})
class HostBindingWithContentChildren {
@ContentChildren('foo')
foos !: QueryList<any>;
@ContentChildren('foo') foos!: QueryList<any>;
}
@Component({
@ -955,7 +966,9 @@ describe('host bindings', () => {
class HostBindingWithContentHooks implements AfterContentInit {
myValue = 'initial';
ngAfterContentInit() { this.myValue = 'after-content'; }
ngAfterContentInit() {
this.myValue = 'after-content';
}
}
@Component({template: '<host-binding-comp></host-binding-comp>'})
@ -971,7 +984,6 @@ describe('host bindings', () => {
});
describe('styles', () => {
it('should bind to host styles', () => {
@Component(
{selector: 'host-binding-to-styles', host: {'[style.width.px]': 'width'}, template: ''})
@ -981,7 +993,7 @@ describe('host bindings', () => {
@Component({template: '<host-binding-to-styles></host-binding-to-styles>'})
class App {
@ViewChild(HostBindingToStyles) hostBindingDir !: HostBindingToStyles;
@ViewChild(HostBindingToStyles) hostBindingDir!: HostBindingToStyles;
}
TestBed.configureTestingModule({declarations: [App, HostBindingToStyles]});
@ -1010,7 +1022,7 @@ describe('host bindings', () => {
@Component({template: '<div hostStyles containerDir></div>'})
class App {
@ViewChild(HostBindingToStyles) hostBindingDir !: HostBindingToStyles;
@ViewChild(HostBindingToStyles) hostBindingDir!: HostBindingToStyles;
}
TestBed.configureTestingModule({declarations: [App, HostBindingToStyles, ContainerDir]});
@ -1044,10 +1056,12 @@ describe('host bindings', () => {
});
describe('sanitization', () => {
function identity(value: any) { return value; }
function verify(
tag: string, prop: string, value: any, expectedSanitizedValue: any, bypassFn: Function,
isAttribute: boolean = true, throws: boolean = false) {
function identity(value: any) {
return value;
}
function verify(tag: string, prop: string, value: any, expectedSanitizedValue: any,
bypassFn: Function, isAttribute: boolean = true,
throws: boolean = false) {
it(`should sanitize <${tag} ${prop}> ${isAttribute ? 'properties' : 'attributes'}`, () => {
@Directive({
selector: '[unsafeUrlHostBindingDir]',
@ -1061,13 +1075,13 @@ describe('host bindings', () => {
@Component({template: `<${tag} unsafeUrlHostBindingDir></${tag}>`})
class App {
@ViewChild(UnsafeDir) unsafeDir !: UnsafeDir;
@ViewChild(UnsafeDir) unsafeDir!: UnsafeDir;
}
TestBed.configureTestingModule({declarations: [App, UnsafeDir]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const el = fixture.nativeElement.querySelector(tag) !;
const el = fixture.nativeElement.querySelector(tag)!;
const current = () => isAttribute ? el.getAttribute(prop) : (el as any)[prop];
fixture.componentInstance.unsafeDir.value = value;
@ -1103,5 +1117,4 @@ describe('host bindings', () => {
'<img src="unsafe:javascript:alert(3)">', bypassSanitizationTrustHtml,
/* isAttribute */ false);
});
});

View File

@ -8,16 +8,17 @@
// Make the `$localize()` global function available to the compiled templates, and the direct calls
// below. This would normally be done inside the application `polyfills.ts` file.
import '@angular/localize/init';
import {CommonModule, registerLocaleData} from '@angular/common';
import localeRo from '@angular/common/locales/ro';
import {Component, ContentChild, ElementRef, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, Pipe, PipeTransform, NO_ERRORS_SCHEMA} from '@angular/core';
import {computeMsgId} from '@angular/compiler';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
import {TestBed} from '@angular/core/testing';
import {loadTranslations, clearTranslations} from '@angular/localize';
import {clearTranslations, loadTranslations} from '@angular/localize';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
import {computeMsgId} from '@angular/compiler';
import {BehaviorSubject} from 'rxjs';
@ -72,7 +73,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('should support interpolations with custom interpolation config', () => {
loadTranslations({[computeMsgId('Hello {$INTERPOLATION}')]: 'Bonjour {$INTERPOLATION}'});
const interpolation = ['{%', '%}'] as[string, string];
const interpolation = ['{%', '%}'] as [string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture = initWithTemplate(AppComp, `<div i18n>Hello {% name %}</div>`);
@ -277,7 +278,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
name = `Angular`;
clicks = 0;
onClick() { this.clicks++; }
onClick() {
this.clicks++;
}
}
TestBed.configureTestingModule({declarations: [ListenerComp]});
@ -619,7 +622,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
[computeMsgId('{VAR_SELECT, select, 10 {ten} other {{INTERPOLATION}}}')]:
'{VAR_SELECT, select, 10 {dix} other {{INTERPOLATION}}}'
});
const interpolation = ['{%', '%}'] as[string, string];
const interpolation = ['{%', '%}'] as [string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, `<div i18n>{count, select, 10 {ten} other {{% name %}}}</div>`);
@ -704,7 +707,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
'{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
});
const fixture = initWithTemplate(
AppComp, `
AppComp,
`
<ng-template i18n tplRef>` +
`{count, select, 10 {ten} 20 {twenty} other {other}}` +
`</ng-template>
@ -864,7 +868,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
private readonly viewContainerRef: ViewContainerRef,
private readonly templateRef: TemplateRef<any>) {}
ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
ngOnInit() {
this.viewContainerRef.createEmbeddedView(this.templateRef);
}
}
@Component({
@ -941,7 +947,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
dir = this;
}
attachEmbeddedView() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
attachEmbeddedView() {
this.viewContainerRef.createEmbeddedView(this.templateRef);
}
}
@Component({
@ -983,7 +991,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe('<my-cmp><!--container--></my-cmp>');
dir !.attachEmbeddedView();
dir!.attachEmbeddedView();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe(
@ -1019,7 +1027,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
class Comp {
type = 'A';
visible = true;
isVisible() { return true; }
isVisible() {
return true;
}
}
TestBed.configureTestingModule({declarations: [Comp]});
@ -1042,7 +1052,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
'{VAR_SELECT, select, A {A - {PH_A}} ' +
'B {B - {PH_B}} other {other - {PH_WITH_SPACES}}}')]:
'{VAR_SELECT, select, A {A (translated) - {PH_A}} ' +
'B {B (translated) - {PH_B}} other {other (translated) - {PH_WITH_SPACES}}}',
'B {B (translated) - {PH_B}} other {other (translated) - {PH_WITH_SPACES}}}',
});
@Component({
selector: 'comp',
@ -1326,7 +1336,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
it('with custom interpolation config', () => {
loadTranslations({[computeMsgId('Hello {$INTERPOLATION}', 'm')]: 'Bonjour {$INTERPOLATION}'});
const interpolation = ['{%', '%}'] as[string, string];
const interpolation = ['{%', '%}'] as [string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, `<div i18n-title="m|d" title="Hello {% name %}"></div>`);
@ -1367,7 +1377,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
})
class TitleDir {
@Input() title = '';
constructor() { titleDirInstances.push(this); }
constructor() {
titleDirInstances.push(this);
}
}
@Component({
@ -1397,7 +1409,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@Directive({selector: '[title]'})
class TitleDir {
@Input() title: string = '';
constructor(public elRef: ElementRef) { titleDirInstances.push(this); }
constructor(public elRef: ElementRef) {
titleDirInstances.push(this);
}
}
@Component({
@ -1429,7 +1443,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
let dirInstance: WithInput;
@Directive({selector: '[dir]'})
class WithInput {
constructor() { dirInstance = this; }
constructor() {
dirInstance = this;
}
@Input() dir: string = '';
}
@ -1445,7 +1461,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
expect(dirInstance !.dir).toBe('Bonjour Angular');
expect(dirInstance!.dir).toBe('Bonjour Angular');
});
it('should allow directive inputs (as interpolated props)' +
@ -1456,7 +1472,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
let dirInstance: WithInput;
@Directive({selector: '[dir]'})
class WithInput {
constructor() { dirInstance = this; }
constructor() {
dirInstance = this;
}
@Input() dir: string = '';
}
@ -1472,7 +1490,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
expect(dirInstance !.dir).toBe('Bonjour Angular');
expect(dirInstance!.dir).toBe('Bonjour Angular');
});
it('should apply i18n attributes during second template pass', () => {
@ -1537,7 +1555,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
template: '{{ messageText }}',
})
class WelcomeComp {
@Input() messageText !: string;
@Input() messageText!: string;
}
@Component({
@ -1598,10 +1616,11 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@Directive({selector: '[test]'})
class ClsDir {
@HostBinding('className')
klass = 'foo';
@HostBinding('className') klass = 'foo';
constructor() { directiveInstances.push(this); }
constructor() {
directiveInstances.push(this);
}
}
@Component({
@ -1641,7 +1660,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
// but IE does not.
expect(outerDiv.getAttribute('title')).toBe('début 2 milieu 1 fin');
expect(outerDiv.getAttribute('class')).toBe('foo');
expect(outerDiv.textContent !.trim()).toBe('traduction: un email');
expect(outerDiv.textContent!.trim()).toBe('traduction: un email');
expect(innerDiv.getAttribute('class')).toBe('foo');
directiveInstances.forEach(instance => instance.klass = 'bar');
@ -1651,7 +1670,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(outerDiv.getAttribute('title')).toBe('début 3 milieu 2 fin');
expect(outerDiv.getAttribute('class')).toBe('bar');
expect(outerDiv.textContent !.trim()).toBe('traduction: 2 emails');
expect(outerDiv.textContent!.trim()).toBe('traduction: 2 emails');
expect(innerDiv.getAttribute('class')).toBe('bar');
});
@ -1660,21 +1679,25 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
let calledValue = false;
@Component({selector: 'my-comp', template: ''})
class MyComp {
t !: string;
t!: string;
@Input()
get title() { return this.t; }
get title() {
return this.t;
}
set title(title) {
calledTitle = true;
this.t = title;
}
@Input()
get value() { return this.val; }
get value() {
return this.val;
}
set value(value: string) {
calledValue = true;
this.val = value;
}
val !: string;
val!: string;
}
TestBed.configureTestingModule({declarations: [AppComp, MyComp]});
@ -2050,7 +2073,6 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
});
it('should display/destroy projected i18n content', () => {
loadTranslations({
[computeMsgId('{VAR_SELECT, select, A {A} B {B} other {other}}')]:
'{VAR_SELECT, select, A {A} B {B} other {other}}'
@ -2109,7 +2131,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@Directive({selector: '[text]', inputs: ['text'], exportAs: 'textDir'})
class TextDirective {
// TODO(issue/24571): remove '!'.
text !: string;
text!: string;
constructor() {}
}
@ -2119,16 +2141,18 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@ContentChild(TemplateRef, {static: true}) template !: TemplateRef<any>;
// TODO(issue/24571): remove '!'.
@ViewChild('vc', {read: ViewContainerRef, static: true})
vc !: ViewContainerRef;
@ViewChild('vc', {read: ViewContainerRef, static: true}) vc!: ViewContainerRef;
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective, {descendants: true})
query !: QueryList<TextDirective>;
@ContentChildren(TextDirective, {descendants: true}) query!: QueryList<TextDirective>;
create() { this.vc.createEmbeddedView(this.template); }
create() {
this.vc.createEmbeddedView(this.template);
}
destroy() { this.vc.clear(); }
destroy() {
this.vc.clear();
}
}
TestBed.configureTestingModule({declarations: [TextDirective, DivQuery]});
@ -2224,7 +2248,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@Directive({selector: 'input'})
class InputsDir {
constructor(private elementRef: ElementRef) {}
ngOnInit() { this.elementRef.nativeElement.value = 'value set in Directive.ngOnInit'; }
ngOnInit() {
this.elementRef.nativeElement.value = 'value set in Directive.ngOnInit';
}
}
@Component({
@ -2248,7 +2274,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
@Directive({selector: 'input'})
class InputsDir {
constructor(private elementRef: ElementRef) {}
ngOnInit() { this.elementRef.nativeElement.value = 'value set in Directive.ngOnInit'; }
ngOnInit() {
this.elementRef.nativeElement.value = 'value set in Directive.ngOnInit';
}
}
@Component({
@ -2401,12 +2429,16 @@ class AppCompWithWhitespaces {
})
class DirectiveWithTplRef {
constructor(public vcRef: ViewContainerRef, public tplRef: TemplateRef<{}>) {}
ngOnInit() { this.vcRef.createEmbeddedView(this.tplRef, {}); }
ngOnInit() {
this.vcRef.createEmbeddedView(this.tplRef, {});
}
}
@Pipe({name: 'uppercase'})
class UppercasePipe implements PipeTransform {
transform(value: string) { return value.toUpperCase(); }
transform(value: string) {
return value.toUpperCase();
}
}
@Directive({selector: `[dialog]`})

View File

@ -16,10 +16,11 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
describe('acceptance integration tests', () => {
function stripHtmlComments(str: string) { return str.replace(/<!--[\s\S]*?-->/g, ''); }
function stripHtmlComments(str: string) {
return str.replace(/<!--[\s\S]*?-->/g, '');
}
describe('render', () => {
it('should render basic template', () => {
@Component({template: '<span title="Hello">Greetings</span>'})
class App {
@ -64,12 +65,10 @@ describe('acceptance integration tests', () => {
tView: 2,
tNode: 4,
});
});
});
describe('ng-container', () => {
it('should insert as a child of a regular element', () => {
@Component(
{template: '<div>before|<ng-container>Greetings<span></span></ng-container>|after</div>'})
@ -108,7 +107,6 @@ describe('acceptance integration tests', () => {
});
it('should add and remove DOM nodes when ng-container is a child of an embedded view', () => {
@Component({template: '<ng-container *ngIf="render">content</ng-container>'})
class App {
render = false;
@ -131,21 +129,24 @@ describe('acceptance integration tests', () => {
// https://stackblitz.com/edit/angular-tfhcz1?file=src%2Fapp%2Fapp.component.ts
it('should add and remove DOM nodes when ng-container is a child of a delayed embedded view',
() => {
@Directive({selector: '[testDirective]'})
class TestDirective {
constructor(private _tplRef: TemplateRef<any>, private _vcRef: ViewContainerRef) {}
createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
createAndInsert() {
this._vcRef.insert(this._tplRef.createEmbeddedView({}));
}
clear() { this._vcRef.clear(); }
clear() {
this._vcRef.clear();
}
}
@Component({
template: '<ng-template testDirective><ng-container>content</ng-container></ng-template>'
})
class App {
@ViewChild(TestDirective, {static: true}) testDirective !: TestDirective;
@ViewChild(TestDirective, {static: true}) testDirective!: TestDirective;
}
TestBed.configureTestingModule({declarations: [App, TestDirective]});
@ -205,9 +206,13 @@ describe('acceptance integration tests', () => {
class TestDirective {
constructor(private _tplRef: TemplateRef<any>, private _vcRef: ViewContainerRef) {}
createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
createAndInsert() {
this._vcRef.insert(this._tplRef.createEmbeddedView({}));
}
clear() { this._vcRef.clear(); }
clear() {
this._vcRef.clear();
}
}
@Component({
@ -215,7 +220,7 @@ describe('acceptance integration tests', () => {
'<ng-template testDirective><ng-container><ng-container><ng-container>content</ng-container></ng-container></ng-container></ng-template>'
})
class App {
@ViewChild(TestDirective, {static: true}) testDirective !: TestDirective;
@ViewChild(TestDirective, {static: true}) testDirective!: TestDirective;
}
TestBed.configureTestingModule({declarations: [App, TestDirective]});
@ -245,7 +250,7 @@ describe('acceptance integration tests', () => {
@Component({template: '<div><ng-container dir></ng-container></div>'})
class App {
@ViewChild(TestDirective) testDirective !: TestDirective;
@ViewChild(TestDirective) testDirective!: TestDirective;
}
TestBed.configureTestingModule({declarations: [App, TestDirective]});
@ -260,14 +265,17 @@ describe('acceptance integration tests', () => {
it('should support ViewContainerRef when ng-container is at the root of a view', () => {
@Directive({selector: '[dir]'})
class TestDirective {
@Input()
contentTpl: TemplateRef<{}>|null = null;
@Input() contentTpl: TemplateRef<{}>|null = null;
constructor(private _vcRef: ViewContainerRef) {}
insertView() { this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>); }
insertView() {
this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>);
}
clear() { this._vcRef.clear(); }
clear() {
this._vcRef.clear();
}
}
@Component({
@ -275,7 +283,7 @@ describe('acceptance integration tests', () => {
'<ng-container dir [contentTpl]="content"><ng-template #content>Content</ng-template></ng-container>'
})
class App {
@ViewChild(TestDirective) testDirective !: TestDirective;
@ViewChild(TestDirective) testDirective!: TestDirective;
}
TestBed.configureTestingModule({declarations: [App, TestDirective]});
@ -298,14 +306,18 @@ describe('acceptance integration tests', () => {
class TestDirective {
constructor(private _tplRef: TemplateRef<{}>, private _vcRef: ViewContainerRef) {}
insertView() { this._vcRef.createEmbeddedView(this._tplRef); }
insertView() {
this._vcRef.createEmbeddedView(this._tplRef);
}
clear() { this._vcRef.clear(); }
clear() {
this._vcRef.clear();
}
}
@Component({template: '<ng-container><ng-template dir>Content</ng-template></ng-container>'})
class App {
@ViewChild(TestDirective) testDirective !: TestDirective;
@ViewChild(TestDirective) testDirective!: TestDirective;
}
TestBed.configureTestingModule({declarations: [App, TestDirective]});
@ -334,7 +346,6 @@ describe('acceptance integration tests', () => {
expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('<div></div>');
});
});
describe('text bindings', () => {
@ -373,11 +384,12 @@ describe('acceptance integration tests', () => {
expect(fixture.nativeElement.innerHTML).toEqual('');
});
});
describe('ngNonBindable handling', () => {
function stripNgNonBindable(str: string) { return str.replace(/ ngnonbindable=""/i, ''); }
function stripNgNonBindable(str: string) {
return str.replace(/ ngnonbindable=""/i, '');
}
it('should keep local ref for host element', () => {
@Component({
@ -404,7 +416,9 @@ describe('acceptance integration tests', () => {
@Directive({selector: '[directive]'})
class TestDirective implements OnInit {
ngOnInit() { directiveInvoked = true; }
ngOnInit() {
directiveInvoked = true;
}
}
@Component({
@ -432,7 +446,9 @@ describe('acceptance integration tests', () => {
@Directive({selector: '[directive]'})
class TestDirective implements OnInit {
ngOnInit() { directiveInvoked = true; }
ngOnInit() {
directiveInvoked = true;
}
}
@Component({
@ -604,14 +620,12 @@ describe('acceptance integration tests', () => {
it('should support a component with binding on host element', () => {
@Component({selector: 'todo', template: '{{title}}'})
class TodoComponentHostBinding {
@HostBinding()
title = 'one';
@HostBinding() title = 'one';
}
@Component({template: '<todo></todo>'})
class App {
@ViewChild(TodoComponentHostBinding)
todoComponentHostBinding !: TodoComponentHostBinding;
@ViewChild(TodoComponentHostBinding) todoComponentHostBinding!: TodoComponentHostBinding;
}
TestBed.configureTestingModule({declarations: [App, TodoComponentHostBinding]});
@ -658,8 +672,7 @@ describe('acceptance integration tests', () => {
it('should support a component with sub-views', () => {
@Component({selector: 'comp', template: '<div *ngIf="condition">text</div>'})
class MyComp {
@Input()
condition !: boolean;
@Input() condition!: boolean;
}
@Component({template: '<comp [condition]="condition"></comp>'})
@ -680,7 +693,6 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
expect(stripHtmlComments(compElement.innerHTML)).toEqual('');
});
});
describe('element bindings', () => {
@ -814,7 +826,7 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
const span: HTMLSpanElement = fixture.nativeElement.querySelector('span');
const bold: HTMLElement = span.querySelector('b') !;
const bold: HTMLElement = span.querySelector('b')!;
fixture.componentInstance.title = 'Hello';
fixture.detectChanges();
@ -840,13 +852,12 @@ describe('acceptance integration tests', () => {
it('should support host attribute bindings', () => {
@Directive({selector: '[hostBindingDir]'})
class HostBindingDir {
@HostBinding('attr.aria-label')
label = 'some label';
@HostBinding('attr.aria-label') label = 'some label';
}
@Component({template: '<div hostBindingDir></div>'})
class App {
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
@ViewChild(HostBindingDir) hostBindingDir!: HostBindingDir;
}
TestBed.configureTestingModule({declarations: [App, HostBindingDir]});
@ -999,12 +1010,13 @@ describe('acceptance integration tests', () => {
it('should apply classes properly when nodes have containers', () => {
@Component({selector: 'structural-comp', template: 'Comp Content'})
class StructuralComp {
@Input()
tmp !: TemplateRef<any>;
@Input() tmp!: TemplateRef<any>;
constructor(public vcr: ViewContainerRef) {}
create() { this.vcr.createEmbeddedView(this.tmp); }
create() {
this.vcr.createEmbeddedView(this.tmp);
}
}
@Component({
@ -1014,7 +1026,7 @@ describe('acceptance integration tests', () => {
`
})
class App {
@ViewChild(StructuralComp) structuralComp !: StructuralComp;
@ViewChild(StructuralComp) structuralComp!: StructuralComp;
value: any;
}
@ -1040,7 +1052,9 @@ describe('acceptance integration tests', () => {
public classesVal: string = '';
@Input('class')
set klass(value: string) { this.classesVal = value; }
set klass(value: string) {
this.classesVal = value;
}
}
@Directive({selector: '[DirWithStyle]'})
@ -1048,15 +1062,16 @@ describe('acceptance integration tests', () => {
public stylesVal: any = '';
@Input()
set style(value: any) { this.stylesVal = value; }
set style(value: any) {
this.stylesVal = value;
}
}
it('should delegate initial classes to a [class] input binding if present on a directive on the same element',
() => {
@Component({template: '<div class="apple orange banana" DirWithClass></div>'})
class App {
@ViewChild(DirWithClassDirective)
mockClassDirective !: DirWithClassDirective;
@ViewChild(DirWithClassDirective) mockClassDirective!: DirWithClassDirective;
}
TestBed.configureTestingModule({declarations: [App, DirWithClassDirective]});
@ -1073,8 +1088,7 @@ describe('acceptance integration tests', () => {
() => {
@Component({template: '<div style="width: 100px; height: 200px" DirWithStyle></div>'})
class App {
@ViewChild(DirWithStyleDirective)
mockStyleDirective !: DirWithStyleDirective;
@ViewChild(DirWithStyleDirective) mockStyleDirective!: DirWithStyleDirective;
}
TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
@ -1092,8 +1106,7 @@ describe('acceptance integration tests', () => {
() => {
@Component({template: '<div DirWithClass [class]="value"></div>'})
class App {
@ViewChild(DirWithClassDirective)
mockClassDirective !: DirWithClassDirective;
@ViewChild(DirWithClassDirective) mockClassDirective!: DirWithClassDirective;
value = '';
}
@ -1111,9 +1124,8 @@ describe('acceptance integration tests', () => {
() => {
@Component({template: '<div DirWithStyle [style]="value"></div>'})
class App {
@ViewChild(DirWithStyleDirective)
mockStyleDirective !: DirWithStyleDirective;
value !: {[key: string]: string};
@ViewChild(DirWithStyleDirective) mockStyleDirective!: DirWithStyleDirective;
value!: {[key: string]: string};
}
TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
@ -1155,7 +1167,7 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
const classes = target.getAttribute('class') !.split(/\s+/).sort();
const classes = target.getAttribute('class')!.split(/\s+/).sort();
expect(classes).toEqual(['big', 'golden', 'heavy']);
expect(target.getAttribute('title')).toEqual('foo');
@ -1189,7 +1201,7 @@ describe('acceptance integration tests', () => {
})
class App {
@ViewChild(DirWithSingleStylingBindings)
dirInstance !: DirWithSingleStylingBindings;
dirInstance!: DirWithSingleStylingBindings;
}
TestBed.configureTestingModule({declarations: [App, DirWithSingleStylingBindings]});
@ -1244,8 +1256,8 @@ describe('acceptance integration tests', () => {
@Component(
{template: '<div Dir1WithStyle Dir2WithStyle [style.width]="width"></div>'})
class App {
@ViewChild(Dir1WithStyle) dir1Instance !: Dir1WithStyle;
@ViewChild(Dir2WithStyle) dir2Instance !: Dir2WithStyle;
@ViewChild(Dir1WithStyle) dir1Instance!: Dir1WithStyle;
@ViewChild(Dir2WithStyle) dir2Instance!: Dir2WithStyle;
width: string|null|undefined = undefined;
}
@ -1309,8 +1321,8 @@ describe('acceptance integration tests', () => {
'<div Dir1WithStyling Dir2WithStyling [style]="stylesExp" [class]="classesExp"></div>'
})
class App {
@ViewChild(Dir1WithStyling) dir1Instance !: Dir1WithStyling;
@ViewChild(Dir2WithStyling) dir2Instance !: Dir2WithStyling;
@ViewChild(Dir1WithStyling) dir1Instance!: Dir1WithStyling;
@ViewChild(Dir2WithStyling) dir2Instance!: Dir2WithStyling;
stylesExp: any = {};
classesExp: any = {};
}
@ -1321,7 +1333,7 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
const {dir1Instance, dir2Instance} = fixture.componentInstance;
const target = fixture.nativeElement.querySelector('div') !;
const target = fixture.nativeElement.querySelector('div')!;
expect(target.style.getPropertyValue('width')).toEqual('111px');
const compInstance = fixture.componentInstance;
@ -1393,7 +1405,7 @@ describe('acceptance integration tests', () => {
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
const target = fixture.nativeElement.querySelector('div') !;
const target = fixture.nativeElement.querySelector('div')!;
expect(target.classList.contains('-fred-36-')).toBeFalsy();
@ -1510,7 +1522,6 @@ describe('acceptance integration tests', () => {
// The ViewEngine error has a typo, whereas the Ivy one fixes it.
/^Unexpected value 'SomeModule' imported by the module 'ModuleWithImportedModule'\. Please add (a|an) @NgModule annotation\.$/);
});
});
it('should only call inherited host listeners once', () => {
@ -1519,7 +1530,9 @@ describe('acceptance integration tests', () => {
@Component({template: ''})
class ButtonSuperClass {
@HostListener('click')
clicked() { clicks++; }
clicked() {
clicks++;
}
}
@Component({selector: 'button[custom-button]', template: ''})
@ -1548,7 +1561,7 @@ describe('acceptance integration tests', () => {
@Component({template: '<div someDir></div>'})
class SuperComp {
@ViewChildren(SomeDir) dirs !: QueryList<SomeDir>;
@ViewChildren(SomeDir) dirs!: QueryList<SomeDir>;
}
@Component({selector: 'button[custom-button]', template: '<div someDir></div>'})
@ -1576,7 +1589,9 @@ describe('acceptance integration tests', () => {
private _isDestroyed = false;
@Input()
get value() { return this._value; }
get value() {
return this._value;
}
set value(newValue: any) {
if (this._isDestroyed) {
throw Error('Cannot assign to value after destroy.');
@ -1586,7 +1601,9 @@ describe('acceptance integration tests', () => {
}
private _value: any;
ngOnDestroy() { this._isDestroyed = true; }
ngOnDestroy() {
this._isDestroyed = true;
}
}
@Component({template: '<div no-assign-after-destroy [value]="directiveValue"></div>'})
@ -1608,7 +1625,7 @@ describe('acceptance integration tests', () => {
@Component(
{selector: 'test-component', template: `foo`, host: {'[attr.aria-disabled]': 'true'}})
class TestComponent {
@ContentChild(TemplateRef, {static: true}) tpl !: TemplateRef<any>;
@ContentChild(TemplateRef, {static: true}) tpl!: TemplateRef<any>;
}
TestBed.configureTestingModule({declarations: [TestComponent]});
@ -1621,7 +1638,7 @@ describe('acceptance integration tests', () => {
it('should inherit inputs from undecorated superclasses', () => {
class ButtonSuperClass {
@Input() isDisabled !: boolean;
@Input() isDisabled!: boolean;
}
@Component({selector: 'button[custom-button]', template: ''})
@ -1651,7 +1668,9 @@ describe('acceptance integration tests', () => {
class ButtonSuperClass {
@Output() clicked = new EventEmitter<void>();
emitClick() { this.clicked.emit(); }
emitClick() {
this.clicked.emit();
}
}
@Component({selector: 'button[custom-button]', template: ''})
@ -1660,7 +1679,9 @@ describe('acceptance integration tests', () => {
@Component({template: '<button custom-button (clicked)="handleClick()"></button>'})
class MyApp {
handleClick() { clicks++; }
handleClick() {
clicks++;
}
}
TestBed.configureTestingModule({declarations: [MyApp, ButtonSubClass]});
@ -1675,8 +1696,7 @@ describe('acceptance integration tests', () => {
it('should inherit host bindings from undecorated superclasses', () => {
class BaseButton {
@HostBinding('attr.tabindex')
tabindex = -1;
@HostBinding('attr.tabindex') tabindex = -1;
}
@Component({selector: '[sub-button]', template: '<ng-content></ng-content>'})
@ -1702,8 +1722,7 @@ describe('acceptance integration tests', () => {
it('should inherit host bindings from undecorated grand superclasses', () => {
class SuperBaseButton {
@HostBinding('attr.tabindex')
tabindex = -1;
@HostBinding('attr.tabindex') tabindex = -1;
}
class BaseButton extends SuperBaseButton {}
@ -1734,7 +1753,9 @@ describe('acceptance integration tests', () => {
class BaseButton {
@HostListener('click')
handleClick() { clicks++; }
handleClick() {
clicks++;
}
}
@Component({selector: '[sub-button]', template: '<ng-content></ng-content>'})
@ -1761,7 +1782,9 @@ describe('acceptance integration tests', () => {
@Directive({selector: '[baseButton]'})
class BaseButton {
@HostListener('click')
handleClick() { clicks++; }
handleClick() {
clicks++;
}
}
@Component({selector: '[subButton]', template: '<ng-content></ng-content>'})
@ -1788,7 +1811,9 @@ describe('acceptance integration tests', () => {
@Directive({selector: '[superBaseButton]'})
class SuperBaseButton {
@HostListener('click')
handleClick() { clicks++; }
handleClick() {
clicks++;
}
}
@Directive({selector: '[baseButton]'})
@ -1819,7 +1844,9 @@ describe('acceptance integration tests', () => {
@Directive({selector: '[superSuperBaseButton]'})
class SuperSuperBaseButton {
@HostListener('click')
handleClick() { clicks++; }
handleClick() {
clicks++;
}
}
@Directive({selector: '[superBaseButton]'})
@ -1855,9 +1882,13 @@ describe('acceptance integration tests', () => {
inputs: ['dir'],
})
class Dir {
get dir(): any { return null; }
get dir(): any {
return null;
}
set dir(value: any) { throw new Error('this error is expected'); }
set dir(value: any) {
throw new Error('this error is expected');
}
}
@Component({
@ -1919,7 +1950,7 @@ describe('acceptance integration tests', () => {
});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges(false);
const element = fixture.nativeElement.querySelector('div') !;
const element = fixture.nativeElement.querySelector('div')!;
assertAttrValues(element, 'first-update-pass');

File diff suppressed because it is too large Load Diff

View File

@ -16,36 +16,40 @@ function getNoOfNativeListeners(): number {
}
describe('event listeners', () => {
describe('coalescing', () => {
@Component({
selector: 'with-clicks-cmpt',
template: `<button likes-clicks (click)="count()" md-button>Click me!</button>`
})
class WithClicksCmpt {
counter = 0;
count() { this.counter++; }
count() {
this.counter++;
}
}
@Directive({selector: '[md-button]'})
class MdButton {
counter = 0;
@HostListener('click')
count() { this.counter++; }
count() {
this.counter++;
}
}
@Directive({selector: '[likes-clicks]'})
class LikesClicks {
counter = 0;
@HostListener('click')
count() { this.counter++; }
count() {
this.counter++;
}
}
@Directive({selector: '[returns-false]'})
class ReturnsFalse {
counter = 0;
event !: Event;
event!: Event;
handlerShouldReturn: boolean|undefined = undefined;
@HostListener('click', ['$event'])
@ -65,7 +69,6 @@ describe('event listeners', () => {
onlyInIvy('ngDevMode.rendererAddEventListener counters are only available in ivy')
.it('should coalesce multiple event listeners for the same event on the same element',
() => {
@Component({
selector: 'test-cmpt',
template:
@ -107,7 +110,6 @@ describe('event listeners', () => {
onlyInIvy('ngDevMode.rendererAddEventListener counters are only available in ivy')
.it('should coalesce multiple event listeners in presence of queries', () => {
@Component({
selector: 'test-cmpt',
template: `<button likes-clicks (click)="counter = counter+1">Click me!</button>`
@ -115,7 +117,7 @@ describe('event listeners', () => {
class TestCmpt {
counter = 0;
@ViewChildren('nothing') nothing !: QueryList<any>;
@ViewChildren('nothing') nothing!: QueryList<any>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, LikesClicks]});
@ -136,11 +138,12 @@ describe('event listeners', () => {
it('should try to execute remaining coalesced listeners if one of the listeners throws', () => {
@Directive({selector: '[throws-on-clicks]'})
class ThrowsOnClicks {
@HostListener('click')
dontCount() { throw new Error('I was clicked and I don\'t like it!'); }
dontCount() {
throw new Error('I was clicked and I don\'t like it!');
}
}
@Component(
@ -151,7 +154,9 @@ describe('event listeners', () => {
let noOfErrors = 0;
class CountingErrorHandler extends ErrorHandler {
handleError(error: any): void { noOfErrors++; }
handleError(error: any): void {
noOfErrors++;
}
}
TestBed.configureTestingModule({
@ -209,7 +214,9 @@ describe('event listeners', () => {
@Input('foo') model: any;
@Output('fooChange') update = new EventEmitter();
updateValue(value: any) { this.update.emit(value); }
updateValue(value: any) {
this.update.emit(value);
}
}
@Component({
@ -222,9 +229,13 @@ describe('event listeners', () => {
@ViewChild(FooDirective) fooDirective: FooDirective|null = null;
fooChange() { this.count++; }
fooChange() {
this.count++;
}
triggerUpdate(value: any) { this.fooDirective !.updateValue(value); }
triggerUpdate(value: any) {
this.fooDirective!.updateValue(value);
}
}
TestBed.configureTestingModule({declarations: [TestComponent, FooDirective]});
@ -247,19 +258,25 @@ describe('event listeners', () => {
})
class MyComp {
counter = 0;
count() { log.push('component.click'); }
count() {
log.push('component.click');
}
}
@Directive({selector: '[dirA]'})
class DirA {
@HostListener('click')
count() { log.push('dirA.click'); }
count() {
log.push('dirA.click');
}
}
@Directive({selector: '[dirB]'})
class DirB {
@HostListener('click')
count() { log.push('dirB.click'); }
count() {
log.push('dirB.click');
}
}
TestBed.configureTestingModule({declarations: [MyComp, DirA, DirB]});

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {CUSTOM_ELEMENTS_SCHEMA, Component, Injectable, InjectionToken, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
import {Component, CUSTOM_ELEMENTS_SCHEMA, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
@ -84,12 +84,16 @@ describe('NgModule', () => {
}
@NgModule({providers: [Service]})
class RoutesModule {
constructor(service: Service) { service.initializations.push('RoutesModule'); }
constructor(service: Service) {
service.initializations.push('RoutesModule');
}
}
@NgModule({imports: [RoutesModule]})
class AppModule {
constructor(service: Service) { service.initializations.push('AppModule'); }
constructor(service: Service) {
service.initializations.push('AppModule');
}
}
TestBed.configureTestingModule({imports: [AppModule]});
@ -188,7 +192,6 @@ describe('NgModule', () => {
onlyInIvy('unknown element check logs a warning rather than throwing')
.it('should warn about unknown element without CUSTOM_ELEMENTS_SCHEMA for element with dash in tag name',
() => {
@Component({template: `<custom-el></custom-el>`})
class MyComp {
}
@ -262,11 +265,12 @@ describe('NgModule', () => {
selectors: [['comp']],
decls: 1,
vars: 0,
template: function MyComp_Template(rf, ctx) {
if (rf & 1) {
element(0, 'custom-el');
}
},
template:
function MyComp_Template(rf, ctx) {
if (rf & 1) {
element(0, 'custom-el');
}
},
encapsulation: 2
});
}
@ -472,7 +476,6 @@ describe('NgModule', () => {
fixture.detectChanges();
}).not.toThrow();
});
});
it('should be able to use DI through the NgModuleRef inside the module constructor', () => {
@ -484,7 +487,9 @@ describe('NgModule', () => {
providers: [{provide: token, useValue: 'foo'}],
})
class TestModule {
constructor(ngRef: NgModuleRef<TestModule>) { value = ngRef.injector.get(token); }
constructor(ngRef: NgModuleRef<TestModule>) {
value = ngRef.injector.get(token);
}
}
TestBed.configureTestingModule({imports: [TestModule], declarations: [TestCmp]});
@ -514,5 +519,4 @@ describe('NgModule', () => {
TestBed.createComponent(TestCmp);
expect(componentInstance).toBeAnInstanceOf(TestCmp);
});
});

View File

@ -40,10 +40,9 @@ onlyInIvy('Debug information exist in ivy only').describe('ngDevMode debug', ()
const element: HTMLElement = fixture.nativeElement;
fixture.detectChanges();
const li = element.querySelector('li') !;
const li = element.querySelector('li')!;
const embeddedLView = loadLContext(li).lView;
expect(embeddedLView.constructor.name).toEqual('LEmbeddedView_MyApp_li_1');
});
});
});

View File

@ -13,29 +13,27 @@ import {TestBed} from '@angular/core/testing';
describe('outputs', () => {
@Component({selector: 'button-toggle', template: ''})
class ButtonToggle {
@Output('change')
change = new EventEmitter();
@Output('change') change = new EventEmitter();
@Output('reset')
resetStream = new EventEmitter();
@Output('reset') resetStream = new EventEmitter();
}
@Directive({selector: '[otherDir]'})
class OtherDir {
@Output('change')
changeStream = new EventEmitter();
@Output('change') changeStream = new EventEmitter();
}
@Component({selector: 'destroy-comp', template: ''})
class DestroyComp implements OnDestroy {
events: string[] = [];
ngOnDestroy() { this.events.push('destroy'); }
ngOnDestroy() {
this.events.push('destroy');
}
}
@Directive({selector: '[myButton]'})
class MyButton {
@Output()
click = new EventEmitter();
@Output() click = new EventEmitter();
}
it('should call component output function when event is emitted', () => {
@ -43,8 +41,10 @@ describe('outputs', () => {
@Component({template: '<button-toggle (change)="onChange()"></button-toggle>'})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
onChange() { counter++; }
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
onChange() {
counter++;
}
}
TestBed.configureTestingModule({declarations: [App, ButtonToggle]});
const fixture = TestBed.createComponent(App);
@ -64,9 +64,13 @@ describe('outputs', () => {
@Component(
{template: '<button-toggle (change)="onChange()" (reset)="onReset()"></button-toggle>'})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
onChange() { counter++; }
onReset() { resetCounter++; }
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
onChange() {
counter++;
}
onReset() {
resetCounter++;
}
}
TestBed.configureTestingModule({declarations: [App, ButtonToggle]});
const fixture = TestBed.createComponent(App);
@ -82,7 +86,7 @@ describe('outputs', () => {
it('should eval component output expression when event is emitted', () => {
@Component({template: '<button-toggle (change)="counter = counter + 1"></button-toggle>'})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
counter = 0;
}
TestBed.configureTestingModule({declarations: [App, ButtonToggle]});
@ -102,10 +106,12 @@ describe('outputs', () => {
@Component(
{template: '<button-toggle *ngIf="condition" (change)="onChange()"></button-toggle>'})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
condition = true;
onChange() { counter++; }
onChange() {
counter++;
}
}
TestBed.configureTestingModule({imports: [CommonModule], declarations: [App, ButtonToggle]});
const fixture = TestBed.createComponent(App);
@ -133,11 +139,13 @@ describe('outputs', () => {
`
})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
condition = true;
condition2 = true;
onChange() { counter++; }
onChange() {
counter++;
}
}
TestBed.configureTestingModule({imports: [CommonModule], declarations: [App, ButtonToggle]});
const fixture = TestBed.createComponent(App);
@ -168,12 +176,16 @@ describe('outputs', () => {
`
})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(DestroyComp) destroyComp !: DestroyComp;
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
@ViewChild(DestroyComp) destroyComp!: DestroyComp;
condition = true;
onClick() { clickCounter++; }
onChange() { changeCounter++; }
onClick() {
clickCounter++;
}
onChange() {
changeCounter++;
}
}
TestBed.configureTestingModule(
{imports: [CommonModule], declarations: [App, ButtonToggle, DestroyComp]});
@ -206,8 +218,10 @@ describe('outputs', () => {
@Component({template: '<button myButton (click)="onClick()">Click me</button>'})
class App {
@ViewChild(MyButton) buttonDir !: MyButton;
onClick() { counter++; }
@ViewChild(MyButton) buttonDir!: MyButton;
onClick() {
counter++;
}
}
TestBed.configureTestingModule({declarations: [App, MyButton]});
const fixture = TestBed.createComponent(App);
@ -228,9 +242,11 @@ describe('outputs', () => {
@Component({template: '<button-toggle (change)="onChange()" otherDir></button-toggle>'})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(OtherDir) otherDir !: OtherDir;
onChange() { counter++; }
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
@ViewChild(OtherDir) otherDir!: OtherDir;
onChange() {
counter++;
}
}
TestBed.configureTestingModule({declarations: [App, ButtonToggle, OtherDir]});
const fixture = TestBed.createComponent(App);
@ -248,8 +264,7 @@ describe('outputs', () => {
@Directive({selector: '[otherChangeDir]'})
class OtherChangeDir {
@Input()
change !: boolean;
@Input() change!: boolean;
}
@Component({
@ -257,11 +272,13 @@ describe('outputs', () => {
'<button-toggle (change)="onChange()" otherChangeDir [change]="change"></button-toggle>'
})
class App {
@ViewChild(ButtonToggle) buttonToggle !: ButtonToggle;
@ViewChild(OtherChangeDir) otherDir !: OtherChangeDir;
@ViewChild(ButtonToggle) buttonToggle!: ButtonToggle;
@ViewChild(OtherChangeDir) otherDir!: OtherChangeDir;
change = true;
onChange() { counter++; }
onChange() {
counter++;
}
}
TestBed.configureTestingModule({declarations: [App, ButtonToggle, OtherChangeDir]});
const fixture = TestBed.createComponent(App);
@ -278,5 +295,4 @@ describe('outputs', () => {
buttonToggle.change.next();
expect(counter).toBe(1);
});
});

View File

@ -14,7 +14,9 @@ describe('pipe', () => {
@Pipe({name: 'countingPipe'})
class CountingPipe implements PipeTransform {
state: number = 0;
transform(value: any) { return `${value} state:${this.state++}`; }
transform(value: any) {
return `${value} state:${this.state++}`;
}
}
@Pipe({name: 'multiArgPipe'})
@ -57,20 +59,21 @@ describe('pipe', () => {
it('should support bindings', () => {
@Directive({selector: '[my-dir]'})
class Dir {
@Input()
dirProp: string = '';
@Input() dirProp: string = '';
}
@Pipe({name: 'double'})
class DoublePipe implements PipeTransform {
transform(value: any) { return `${value}${value}`; }
transform(value: any) {
return `${value}${value}`;
}
}
@Component({
template: `<div my-dir [dirProp]="'a'|double"></div>`,
})
class App {
@ViewChild(Dir) directive !: Dir;
@ViewChild(Dir) directive!: Dir;
}
TestBed.configureTestingModule({declarations: [App, DoublePipe, Dir]});
@ -113,7 +116,9 @@ describe('pipe', () => {
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
@Pipe({name: 'number'})
class PipeA implements PipeTransform {
transform(value: any) { return `PipeA: ${value}`; }
transform(value: any) {
return `PipeA: ${value}`;
}
}
@NgModule({
@ -125,7 +130,9 @@ describe('pipe', () => {
@Pipe({name: 'number'})
class PipeB implements PipeTransform {
transform(value: any) { return `PipeB: ${value}`; }
transform(value: any) {
return `PipeB: ${value}`;
}
}
@Component({
@ -150,7 +157,9 @@ describe('pipe', () => {
() => {
@Pipe({name: 'number'})
class PipeA implements PipeTransform {
transform(value: any) { return `PipeA: ${value}`; }
transform(value: any) {
return `PipeA: ${value}`;
}
}
@NgModule({
@ -162,7 +171,9 @@ describe('pipe', () => {
@Pipe({name: 'number'})
class PipeB implements PipeTransform {
transform(value: any) { return `PipeB: ${value}`; }
transform(value: any) {
return `PipeB: ${value}`;
}
}
@NgModule({
@ -222,12 +233,16 @@ describe('pipe', () => {
it('should support duplicates by using the later entry', () => {
@Pipe({name: 'duplicatePipe'})
class DuplicatePipe1 implements PipeTransform {
transform(value: any) { return `${value} from duplicate 1`; }
transform(value: any) {
return `${value} from duplicate 1`;
}
}
@Pipe({name: 'duplicatePipe'})
class DuplicatePipe2 implements PipeTransform {
transform(value: any) { return `${value} from duplicate 2`; }
transform(value: any) {
return `${value} from duplicate 2`;
}
}
@Component({
@ -247,7 +262,9 @@ describe('pipe', () => {
it('should support pipe in context of ternary operator', () => {
@Pipe({name: 'pipe'})
class MyPipe implements PipeTransform {
transform(value: any): any { return value; }
transform(value: any): any {
return value;
}
}
@Component({
@ -313,8 +330,12 @@ describe('pipe', () => {
@Pipe({name: 'countingImpurePipe', pure: false})
class CountingImpurePipe implements PipeTransform {
state: number = 0;
transform(value: any) { return `${value} state:${this.state++}`; }
constructor() { impurePipeInstances.push(this); }
transform(value: any) {
return `${value} state:${this.state++}`;
}
constructor() {
impurePipeInstances.push(this);
}
}
beforeEach(() => impurePipeInstances = []);
@ -372,8 +393,12 @@ describe('pipe', () => {
@Pipe({name: 'pipeWithOnDestroy'})
class PipeWithOnDestroy implements PipeTransform, OnDestroy {
ngOnDestroy() { destroyCalls++; }
transform(value: any): any { return null; }
ngOnDestroy() {
destroyCalls++;
}
transform(value: any): any {
return null;
}
}
@Component({
@ -401,7 +426,9 @@ describe('pipe', () => {
@Pipe({name: 'myConcatPipe'})
class ConcatPipe implements PipeTransform {
constructor(public service: Service) {}
transform(value: string): string { return `${value} - ${this.service.title}`; }
transform(value: string): string {
return `${value} - ${this.service.title}`;
}
}
@Component({
@ -428,7 +455,9 @@ describe('pipe', () => {
@Pipe({name: 'myConcatPipe'})
class ConcatPipe implements PipeTransform {
constructor(@Inject(token) public service: Service) {}
transform(value: string): string { return `${value} - ${this.service.title}`; }
transform(value: string): string {
return `${value} - ${this.service.title}`;
}
}
@Component({
@ -461,7 +490,9 @@ describe('pipe', () => {
@Pipe({name: 'myConcatPipe'})
class ConcatPipe implements PipeTransform {
constructor(public service: Service) {}
transform(value: string): string { return `${value} - ${this.service.title}`; }
transform(value: string): string {
return `${value} - ${this.service.title}`;
}
}
@Component({
@ -501,7 +532,7 @@ describe('pipe', () => {
})
class App {
@Input() something: any;
@ViewChild(SomeComp) comp !: SomeComp;
@ViewChild(SomeComp) comp!: SomeComp;
pipeValue = 10;
displayValue = 0;
}
@ -512,7 +543,9 @@ describe('pipe', () => {
pipeChangeDetectorRef = changeDetectorRef;
}
transform() { return ''; }
transform() {
return '';
}
}
TestBed.configureTestingModule({declarations: [App, SomeComp, TestPipe]});
@ -521,7 +554,7 @@ describe('pipe', () => {
fixture.componentInstance.displayValue = 1;
fixture.componentInstance.comp.displayValue = 1;
pipeChangeDetectorRef !.markForCheck();
pipeChangeDetectorRef!.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Outer value: "1"');
@ -553,7 +586,7 @@ describe('pipe', () => {
})
class App {
@Input() something: any;
@ViewChild(SomeComp) comp !: SomeComp;
@ViewChild(SomeComp) comp!: SomeComp;
pipeValue = 10;
displayValue = 0;
}
@ -564,7 +597,9 @@ describe('pipe', () => {
pipeChangeDetectorRef = changeDetectorRef;
}
transform() { return ''; }
transform() {
return '';
}
}
TestBed.configureTestingModule({declarations: [App, SomeComp, TestPipe]});
@ -573,22 +608,21 @@ describe('pipe', () => {
fixture.componentInstance.displayValue = 1;
fixture.componentInstance.comp.displayValue = 1;
pipeChangeDetectorRef !.markForCheck();
pipeChangeDetectorRef!.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Outer value: "1"');
expect(fixture.nativeElement.textContent).toContain('Inner value: "0"');
});
});
describe('pure pipe error handling', () => {
it('should not re-invoke pure pipes if it fails initially', () => {
@Pipe({name: 'throwPipe', pure: true})
class ThrowPipe implements PipeTransform {
transform(): never { throw new Error('ThrowPipeError'); }
transform(): never {
throw new Error('ThrowPipeError');
}
}
@Component({template: `{{val | throwPipe}}`})
class App {
@ -607,7 +641,6 @@ describe('pipe', () => {
it('should display the last known result from a pure pipe when it throws', () => {
@Pipe({name: 'throwPipe', pure: true})
class ThrowPipe implements PipeTransform {
transform(value: string): string {
@ -647,7 +680,8 @@ describe('pipe', () => {
describe('pure pipe error handling with multiple arguments', () => {
const args: string[] = new Array(10).fill(':0');
for (let numberOfPipeArgs = 0; numberOfPipeArgs < args.length; numberOfPipeArgs++) {
it(`should not invoke ${numberOfPipeArgs} argument pure pipe second time if it throws unless input changes`,
it(`should not invoke ${
numberOfPipeArgs} argument pure pipe second time if it throws unless input changes`,
() => {
// https://stackblitz.com/edit/angular-mbx2pg
const log: string[] = [];
@ -685,8 +719,5 @@ describe('pipe', () => {
});
}
});
});
});

View File

@ -91,7 +91,6 @@ describe('property bindings', () => {
it('should not map properties whose names do not correspond to their attribute names, ' +
'if they correspond to inputs',
() => {
@Component({template: '', selector: 'my-comp'})
class MyComp {
@Input() for !:string;
@ -393,7 +392,6 @@ describe('property bindings', () => {
});
describe('attributes and input properties', () => {
@Directive({selector: '[myDir]', exportAs: 'myDir'})
class MyDir {
@Input() role: string|undefined;
@ -596,7 +594,6 @@ describe('property bindings', () => {
expect(comp2.children[0].getAttribute('role')).toBe('button');
expect(comp2.textContent).toBe('role: button');
});
});
it('should not throw on synthetic property bindings when a directive on the same element injects ViewContainerRef',
@ -626,5 +623,4 @@ describe('property bindings', () => {
fixture.detectChanges();
}).not.toThrow();
});
});

View File

@ -8,7 +8,7 @@
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {of } from 'rxjs';
import {of} from 'rxjs';
describe('property interpolation', () => {
it('should handle all flavors of interpolated properties', () => {
@ -67,7 +67,7 @@ describe('property interpolation', () => {
`
})
class App {
details = of ({
details = of({
title: 'cool image',
url: 'http://somecooldomain:1234/cool_image.png',
});
@ -93,7 +93,11 @@ describe('property interpolation', () => {
/** Clearly this is a doctor of heavy metals. */
leadSurgeon = {
getCommonInfo() {
return {getPhotoUrl() { return 'http://somecooldomain:1234/cool_image.png'; }};
return {
getPhotoUrl() {
return 'http://somecooldomain:1234/cool_image.png';
}
};
}
};
}

View File

@ -18,8 +18,7 @@ describe('components using pure function instructions internally', () => {
template: ``,
})
class MyComp {
@Input()
names: string[] = [];
@Input() names: string[] = [];
}
@ -60,7 +59,7 @@ describe('components using pure function instructions internally', () => {
myComp.names = ['should not be overwritten'];
fixture.detectChanges();
expect(myComp !.names).toEqual(['should not be overwritten']);
expect(myComp!.names).toEqual(['should not be overwritten']);
});
@ -91,11 +90,9 @@ describe('components using pure function instructions internally', () => {
template: ``,
})
class ManyPropComp {
@Input()
names1: string[] = [];
@Input() names1: string[] = [];
@Input()
names2: string[] = [];
@Input() names2: string[] = [];
}
@Component({
@ -117,14 +114,14 @@ describe('components using pure function instructions internally', () => {
fixture.detectChanges();
const manyPropComp = fixture.debugElement.query(By.directive(ManyPropComp)).componentInstance;
expect(manyPropComp !.names1).toEqual(['Nancy', 'Carson']);
expect(manyPropComp !.names2).toEqual(['George']);
expect(manyPropComp!.names1).toEqual(['Nancy', 'Carson']);
expect(manyPropComp!.names2).toEqual(['George']);
fixture.componentInstance.customName = 'George';
fixture.componentInstance.customName2 = 'Carson';
fixture.detectChanges();
expect(manyPropComp !.names1).toEqual(['Nancy', 'George']);
expect(manyPropComp !.names2).toEqual(['Carson']);
expect(manyPropComp!.names1).toEqual(['Nancy', 'George']);
expect(manyPropComp!.names2).toEqual(['Carson']);
});
@ -222,7 +219,6 @@ describe('components using pure function instructions internally', () => {
it('should work up to 8 bindings', () => {
@Component({
template: `
<my-comp [names]="['a', 'b', 'c', 'd', 'e', 'f', 'g', v8]"></my-comp>
@ -346,8 +342,7 @@ describe('components using pure function instructions internally', () => {
template: ``,
})
class ObjectComp {
@Input()
config: any = [];
@Input() config: any = [];
}
it('should support an object literal', () => {
@ -495,7 +490,7 @@ describe('components using pure function instructions internally', () => {
`
})
class App {
@ViewChildren(Dir) directives !: QueryList<Dir>;
@ViewChildren(Dir) directives!: QueryList<Dir>;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -514,7 +509,7 @@ describe('components using pure function instructions internally', () => {
`
})
class App {
@ViewChildren(Dir) directives !: QueryList<Dir>;
@ViewChildren(Dir) directives!: QueryList<Dir>;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -528,7 +523,7 @@ describe('components using pure function instructions internally', () => {
it('should not share object literals across component instances', () => {
@Component({template: `<div [dir]="{}"></div>`})
class App {
@ViewChild(Dir) directive !: Dir;
@ViewChild(Dir) directive!: Dir;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -545,7 +540,7 @@ describe('components using pure function instructions internally', () => {
it('should not share array literals across component instances', () => {
@Component({template: `<div [dir]="[]"></div>`})
class App {
@ViewChild(Dir) directive !: Dir;
@ViewChild(Dir) directive!: Dir;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -567,7 +562,7 @@ describe('components using pure function instructions internally', () => {
`
})
class App {
@ViewChildren(Dir) directives !: QueryList<Dir>;
@ViewChildren(Dir) directives!: QueryList<Dir>;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -586,7 +581,7 @@ describe('components using pure function instructions internally', () => {
`
})
class App {
@ViewChildren(Dir) directives !: QueryList<Dir>;
@ViewChildren(Dir) directives!: QueryList<Dir>;
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -605,8 +600,10 @@ describe('components using pure function instructions internally', () => {
`
})
class App {
@ViewChildren(Dir) directives !: QueryList<Dir>;
getFoo() { return 'foo!'; }
@ViewChildren(Dir) directives!: QueryList<Dir>;
getFoo() {
return 'foo!';
}
}
TestBed.configureTestingModule({declarations: [Dir, App]});
@ -616,8 +613,5 @@ describe('components using pure function instructions internally', () => {
expect(values).toEqual([{foo: null}, {foo: 'foo!'}]);
});
});
});

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef, forwardRef} from '@angular/core';
import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -215,7 +215,7 @@ describe('query logic', () => {
}
class MyComp {
@ViewChildren(SomeDir) foo !: QueryList<SomeDir>;
@ViewChildren(SomeDir) foo!: QueryList<SomeDir>;
}
@Component({
@ -242,7 +242,7 @@ describe('query logic', () => {
}
class MySuperComp {
@ViewChildren(SomeDir) foo !: QueryList<SomeDir>;
@ViewChildren(SomeDir) foo!: QueryList<SomeDir>;
}
class MyComp extends MySuperComp {}
@ -275,7 +275,7 @@ describe('query logic', () => {
template: `<ng-container [ngTemplateOutlet]="content"></ng-container>`
})
class Insertion {
@Input() content !: TemplateRef<{}>;
@Input() content!: TemplateRef<{}>;
}
@Component({
@ -287,7 +287,7 @@ describe('query logic', () => {
`
})
class App {
@ViewChild(Required) requiredEl !: Required;
@ViewChild(Required) requiredEl!: Required;
viewChildAvailableInAfterViewInit?: boolean;
ngAfterViewInit() {
@ -300,7 +300,6 @@ describe('query logic', () => {
fixture.detectChanges();
expect(fixture.componentInstance.viewChildAvailableInAfterViewInit).toBe(true);
});
});
describe('content queries', () => {
@ -538,7 +537,7 @@ describe('query logic', () => {
@Component({template: '<sub-comp><div #foo></div></sub-comp>'})
class App {
@ViewChild(SubComp) subComp !: SubComp;
@ViewChild(SubComp) subComp!: SubComp;
}
TestBed.configureTestingModule({declarations: [App, SubComp]});
@ -561,7 +560,7 @@ describe('query logic', () => {
@Component({template: '<sub-comp><div #foo></div></sub-comp>'})
class App {
@ViewChild(SubComp) subComp !: SubComp;
@ViewChild(SubComp) subComp!: SubComp;
}
TestBed.configureTestingModule({declarations: [App, SubComp]});
@ -577,7 +576,7 @@ describe('query logic', () => {
}
class MyComp {
@ContentChildren(SomeDir) foo !: QueryList<SomeDir>;
@ContentChildren(SomeDir) foo!: QueryList<SomeDir>;
}
@Component({selector: 'sub-comp', template: '<ng-content></ng-content>'})
@ -593,7 +592,7 @@ describe('query logic', () => {
`
})
class App {
@ViewChild(SubComp) subComp !: SubComp;
@ViewChild(SubComp) subComp!: SubComp;
}
TestBed.configureTestingModule({declarations: [App, SubComp, SomeDir]});
@ -610,7 +609,7 @@ describe('query logic', () => {
}
class MySuperComp {
@ContentChildren(SomeDir) foo !: QueryList<SomeDir>;
@ContentChildren(SomeDir) foo!: QueryList<SomeDir>;
}
class MyComp extends MySuperComp {}
@ -628,7 +627,7 @@ describe('query logic', () => {
`
})
class App {
@ViewChild(SubComp) subComp !: SubComp;
@ViewChild(SubComp) subComp!: SubComp;
}
TestBed.configureTestingModule({declarations: [App, SubComp, SomeDir]});
@ -657,7 +656,7 @@ describe('query logic', () => {
template: '',
})
class ShallowComp {
@ContentChildren('foo', {descendants: false}) foos !: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: false}) foos!: QueryList<ElementRef>;
}
TestBed.configureTestingModule(
@ -666,7 +665,7 @@ describe('query logic', () => {
fixture.detectChanges();
const shallowComp = fixture.debugElement.query(By.directive(ShallowComp)).componentInstance;
const queryList = shallowComp !.foos;
const queryList = shallowComp!.foos;
expect(queryList.length).toBe(0);
fixture.componentInstance.showing = true;
@ -691,24 +690,24 @@ describe('query logic', () => {
});
describe('descendants: false (default)', () => {
/**
* A helper function to check if a given object looks like ElementRef. It is used in place of
* the `instanceof ElementRef` check since ivy returns a type that looks like ElementRef (have
* the same properties but doesn't pass the instanceof ElementRef test)
*/
function isElementRefLike(result: any): boolean { return result.nativeElement != null; }
function isElementRefLike(result: any): boolean {
return result.nativeElement != null;
}
it('should match directives on elements that used to be wrapped by a required parent in HTML parser',
() => {
@Directive({selector: '[myDef]'})
class MyDef {
}
@Component({selector: 'my-container', template: ``})
class MyContainer {
@ContentChildren(MyDef) myDefs !: QueryList<MyDef>;
@ContentChildren(MyDef) myDefs!: QueryList<MyDef>;
}
@Component(
{selector: 'test-cmpt', template: `<my-container><tr myDef></tr></my-container>`})
@ -724,10 +723,9 @@ describe('query logic', () => {
});
it('should match elements with local refs inside <ng-container>', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren('target') targets !: QueryList<ElementRef>;
@ContentChildren('target') targets!: QueryList<ElementRef>;
}
@Component({
selector: 'test-cmpt',
@ -752,10 +750,9 @@ describe('query logic', () => {
});
it('should match elements with local refs inside nested <ng-container>', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren('target') targets !: QueryList<ElementRef>;
@ContentChildren('target') targets!: QueryList<ElementRef>;
}
@Component({
@ -791,7 +788,7 @@ describe('query logic', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets !: QueryList<HTMLElement>;
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
@ -823,7 +820,7 @@ describe('query logic', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets !: QueryList<HTMLElement>;
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
@ -859,7 +856,7 @@ describe('query logic', () => {
@Directive({selector: '[needs-target]'})
class NeedsTarget {
@ContentChildren(TargetDir) targets !: QueryList<HTMLElement>;
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
@ -893,8 +890,8 @@ describe('query logic', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) dirTargets !: QueryList<TargetDir>;
@ContentChildren('target') localRefsTargets !: QueryList<ElementRef>;
@ContentChildren(TargetDir) dirTargets!: QueryList<TargetDir>;
@ContentChildren('target') localRefsTargets!: QueryList<ElementRef>;
}
@Component({
@ -931,7 +928,7 @@ describe('query logic', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets !: QueryList<HTMLElement>;
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
@ -963,7 +960,6 @@ describe('query logic', () => {
describe('observable interface', () => {
it('should allow observing changes to query list', () => {
const fixture = TestBed.createComponent(QueryCompWithChanges);
let changes = 0;
@ -983,13 +979,10 @@ describe('query logic', () => {
fixture.detectChanges();
expect(changes).toBe(1);
});
});
describe('view boundaries', () => {
describe('ViewContainerRef', () => {
@Directive({selector: '[vc]', exportAs: 'vc'})
class ViewContainerManipulatorDirective {
constructor(private _vcRef: ViewContainerRef) {}
@ -998,9 +991,13 @@ describe('query logic', () => {
return this._vcRef.createEmbeddedView(tpl, ctx, idx);
}
remove(index?: number) { this._vcRef.remove(index); }
remove(index?: number) {
this._vcRef.remove(index);
}
move(viewRef: ViewRef, index: number) { this._vcRef.move(viewRef, index); }
move(viewRef: ViewRef, index: number) {
this._vcRef.move(viewRef, index);
}
}
it('should report results in views inserted / removed by ngIf', () => {
@ -1014,7 +1011,7 @@ describe('query logic', () => {
})
class TestComponent {
value: boolean = false;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChildren('foo') query!: QueryList<any>;
}
TestBed.configureTestingModule({declarations: [TestComponent]});
@ -1045,7 +1042,7 @@ describe('query logic', () => {
})
class TestComponent {
value: string[]|undefined;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChildren('foo') query!: QueryList<any>;
}
TestBed.configureTestingModule({declarations: [TestComponent]});
@ -1082,7 +1079,6 @@ describe('query logic', () => {
*/
it('should notify on changes when a given view is removed and re-inserted at the same index',
() => {
@Component({
selector: 'test-comp',
template: `
@ -1093,10 +1089,9 @@ describe('query logic', () => {
class TestComponent implements AfterViewInit {
queryListNotificationCounter = 0;
@ViewChild(ViewContainerManipulatorDirective)
vc !: ViewContainerManipulatorDirective;
@ViewChild('tpl') tpl !: TemplateRef<any>;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective;
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ViewChildren('foo') query!: QueryList<any>;
ngAfterViewInit() {
this.query.changes.subscribe(() => this.queryListNotificationCounter++);
@ -1125,13 +1120,13 @@ describe('query logic', () => {
it('should support a mix of content queries from the declaration and embedded view', () => {
@Directive({selector: '[query-for-lots-of-content]'})
class QueryForLotsOfContent {
@ContentChildren('foo', {descendants: true}) foos1 !: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos2 !: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos1!: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos2!: QueryList<ElementRef>;
}
@Directive({selector: '[query-for-content]'})
class QueryForContent {
@ContentChildren('foo') foos !: QueryList<ElementRef>;
@ContentChildren('foo') foos!: QueryList<ElementRef>;
}
@Component({
@ -1192,11 +1187,10 @@ describe('query logic', () => {
`,
})
class TestComponent {
@ViewChild(ViewContainerManipulatorDirective)
vc !: ViewContainerManipulatorDirective;
@ViewChild('tpl1') tpl1 !: TemplateRef<any>;
@ViewChild('tpl2') tpl2 !: TemplateRef<any>;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective;
@ViewChild('tpl1') tpl1!: TemplateRef<any>;
@ViewChild('tpl2') tpl2!: TemplateRef<any>;
@ViewChildren('foo') query!: QueryList<any>;
}
TestBed.configureTestingModule(
@ -1210,8 +1204,8 @@ describe('query logic', () => {
expect(queryList.length).toBe(1);
expect(queryList.first.nativeElement.getAttribute('id')).toBe('middle');
vc.insertTpl(tpl1 !, {idx: 0}, 0);
vc.insertTpl(tpl2 !, {idx: 1}, 1);
vc.insertTpl(tpl1!, {idx: 0}, 0);
vc.insertTpl(tpl2!, {idx: 1}, 1);
fixture.detectChanges();
expect(queryList.length).toBe(3);
@ -1220,7 +1214,7 @@ describe('query logic', () => {
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
vc.insertTpl(tpl1 !, {idx: 1}, 1);
vc.insertTpl(tpl1!, {idx: 1}, 1);
fixture.detectChanges();
expect(queryList.length).toBe(4);
@ -1264,10 +1258,10 @@ describe('query logic', () => {
`,
})
class TestComponent {
@ViewChild('tpl') tpl !: TemplateRef<any>;
@ViewChild('vi0') vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1') vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ViewChild('vi0') vi0!: ViewContainerManipulatorDirective;
@ViewChild('vi1') vi1!: ViewContainerManipulatorDirective;
@ViewChildren('foo') query!: QueryList<any>;
}
TestBed.configureTestingModule(
@ -1280,8 +1274,8 @@ describe('query logic', () => {
expect(queryList.length).toBe(0);
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
vi0.insertTpl(tpl!, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl!, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges();
expect(queryList.length).toBe(2);
@ -1315,7 +1309,7 @@ describe('query logic', () => {
})
class MyApp {
show = false;
@ViewChildren('foo') query !: QueryList<any>;
@ViewChildren('foo') query!: QueryList<any>;
}
TestBed.configureTestingModule({declarations: [MyApp], imports: [CommonModule]});
@ -1334,14 +1328,11 @@ describe('query logic', () => {
fixture.detectChanges();
expect(queryList.length).toBe(0);
});
});
});
describe('non-regression', () => {
it('should query by provider super-type in an embedded view', () => {
@Directive({selector: '[child]'})
class Child {
}
@ -1356,7 +1347,7 @@ describe('query logic', () => {
`<ng-template [ngIf]="true"><ng-template [ngIf]="true"><div parent></div></ng-template></ng-template>`
})
class TestCmpt {
@ViewChildren(Child) instances !: QueryList<Child>;
@ViewChildren(Child) instances!: QueryList<Child>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, Parent, Child]});
@ -1367,7 +1358,6 @@ describe('query logic', () => {
});
it('should flatten multi-provider results', () => {
class MyClass {}
@Component({
@ -1381,7 +1371,7 @@ describe('query logic', () => {
@Component({selector: 'test-cmpt', template: `<with-multi-provider></with-multi-provider>`})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
@ViewChildren(MyClass) queryResults!: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
@ -1393,7 +1383,6 @@ describe('query logic', () => {
});
it('should flatten multi-provider results when crossing ng-template', () => {
class MyClass {}
@Component({
@ -1413,7 +1402,7 @@ describe('query logic', () => {
`
})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
@ViewChildren(MyClass) queryResults!: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
@ -1444,7 +1433,7 @@ describe('query logic', () => {
`
})
class App {
@ViewChild(GroupDir) group !: GroupDir;
@ViewChild(GroupDir) group!: GroupDir;
}
TestBed.configureTestingModule(
@ -1481,7 +1470,7 @@ describe('query logic', () => {
`
})
class App {
@ViewChildren(GroupDir) groups !: QueryList<GroupDir>;
@ViewChildren(GroupDir) groups!: QueryList<GroupDir>;
}
TestBed.configureTestingModule(
@ -1497,9 +1486,7 @@ describe('query logic', () => {
expect(groups[1]).toBeAnInstanceOf(GroupDir);
expect(groups[2]).toBeUndefined();
});
});
});
function initWithTemplate(compType: Type<any>, template: string) {
@ -1511,11 +1498,11 @@ function initWithTemplate(compType: Type<any>, template: string) {
@Component({selector: 'local-ref-query-component', template: '<ng-content></ng-content>'})
class QueryComp {
@ViewChild('viewQuery') viewChild !: any;
@ContentChild('contentQuery') contentChild !: any;
@ViewChild('viewQuery') viewChild!: any;
@ContentChild('contentQuery') contentChild!: any;
@ViewChildren('viewQuery') viewChildren !: QueryList<any>;
@ContentChildren('contentQuery') contentChildren !: QueryList<any>;
@ViewChildren('viewQuery') viewChildren!: QueryList<any>;
@ContentChildren('contentQuery') contentChildren!: QueryList<any>;
}
@Component({selector: 'app-comp', template: ``})
@ -1543,12 +1530,14 @@ class TextDirective {
`
})
class StaticViewQueryComp {
private _textDir !: TextDirective;
private _foo !: ElementRef;
private _textDir!: TextDirective;
private _foo!: ElementRef;
setEvents: string[] = [];
@ViewChild(TextDirective, {static: true})
get textDir(): TextDirective { return this._textDir; }
get textDir(): TextDirective {
return this._textDir;
}
set textDir(value: TextDirective) {
this.setEvents.push('textDir set');
@ -1556,7 +1545,9 @@ class StaticViewQueryComp {
}
@ViewChild('foo')
get foo(): ElementRef { return this._foo; }
get foo(): ElementRef {
return this._foo;
}
set foo(value: ElementRef) {
this.setEvents.push('foo set');
@ -1577,22 +1568,22 @@ class StaticViewQueryComp {
`
})
class SubclassStaticViewQueryComp extends StaticViewQueryComp {
@ViewChild('bar', {static: true})
bar !: ElementRef;
@ViewChild('bar', {static: true}) bar!: ElementRef;
@ViewChild('baz')
baz !: ElementRef;
@ViewChild('baz') baz!: ElementRef;
}
@Component({selector: 'static-content-query-comp', template: `<ng-content></ng-content>`})
class StaticContentQueryComp {
private _textDir !: TextDirective;
private _foo !: ElementRef;
private _textDir!: TextDirective;
private _foo!: ElementRef;
setEvents: string[] = [];
@ContentChild(TextDirective, {static: true})
get textDir(): TextDirective { return this._textDir; }
get textDir(): TextDirective {
return this._textDir;
}
set textDir(value: TextDirective) {
this.setEvents.push('textDir set');
@ -1600,7 +1591,9 @@ class StaticContentQueryComp {
}
@ContentChild('foo')
get foo(): ElementRef { return this._foo; }
get foo(): ElementRef {
return this._foo;
}
set foo(value: ElementRef) {
this.setEvents.push('foo set');
@ -1610,12 +1603,14 @@ class StaticContentQueryComp {
@Directive({selector: '[staticContentQueryDir]'})
class StaticContentQueryDir {
private _textDir !: TextDirective;
private _foo !: ElementRef;
private _textDir!: TextDirective;
private _foo!: ElementRef;
setEvents: string[] = [];
@ContentChild(TextDirective, {static: true})
get textDir(): TextDirective { return this._textDir; }
get textDir(): TextDirective {
return this._textDir;
}
set textDir(value: TextDirective) {
this.setEvents.push('textDir set');
@ -1623,7 +1618,9 @@ class StaticContentQueryDir {
}
@ContentChild('foo')
get foo(): ElementRef { return this._foo; }
get foo(): ElementRef {
return this._foo;
}
set foo(value: ElementRef) {
this.setEvents.push('foo set');
@ -1633,11 +1630,9 @@ class StaticContentQueryDir {
@Component({selector: 'subclass-static-content-query-comp', template: `<ng-content></ng-content>`})
class SubclassStaticContentQueryComp extends StaticContentQueryComp {
@ContentChild('bar', {static: true})
bar !: ElementRef;
@ContentChild('bar', {static: true}) bar!: ElementRef;
@ContentChild('baz')
baz !: ElementRef;
@ContentChild('baz') baz!: ElementRef;
}
@Component({
@ -1647,7 +1642,7 @@ class SubclassStaticContentQueryComp extends StaticContentQueryComp {
`
})
export class QueryCompWithChanges {
@ViewChildren('foo') foos !: QueryList<any>;
@ViewChildren('foo') foos!: QueryList<any>;
showing = false;
}
@ -1658,7 +1653,7 @@ class SuperDirectiveQueryTarget {
@Directive({selector: '[super-directive]'})
class SuperDirective {
@ViewChildren(SuperDirectiveQueryTarget) headers !: QueryList<SuperDirectiveQueryTarget>;
@ViewChildren(SuperDirectiveQueryTarget) headers!: QueryList<SuperDirectiveQueryTarget>;
}
@Component({

View File

@ -24,19 +24,29 @@ describe('renderer factory lifecycle', () => {
@Component({selector: 'some-component', template: `foo`})
class SomeComponent implements DoCheck {
ngOnInit() { logs.push('some_component create'); }
ngDoCheck() { logs.push('some_component update'); }
ngOnInit() {
logs.push('some_component create');
}
ngDoCheck() {
logs.push('some_component update');
}
}
@Component({selector: 'some-component-with-error', template: `With error`})
class SomeComponentWhichThrows {
ngOnInit() { throw new Error('SomeComponentWhichThrows threw'); }
ngOnInit() {
throw new Error('SomeComponentWhichThrows threw');
}
}
@Component({selector: 'lol', template: `<some-component></some-component>`})
class TestComponent implements DoCheck {
ngOnInit() { logs.push('test_component create'); }
ngDoCheck() { logs.push('test_component update'); }
ngOnInit() {
logs.push('test_component create');
}
ngDoCheck() {
logs.push('test_component update');
}
}
/** Creates a patched renderer factory that pushes entries to the test log */
@ -44,7 +54,7 @@ describe('renderer factory lifecycle', () => {
let rendererFactory = getRendererFactory2(document);
const createRender = rendererFactory.createRenderer;
rendererFactory.createRenderer = (hostElement: any, type: RendererType2 | null) => {
rendererFactory.createRenderer = (hostElement: any, type: RendererType2|null) => {
logs.push('create');
return createRender.apply(rendererFactory, [hostElement, type]);
};
@ -168,7 +178,7 @@ describe('animation renderer factory', () => {
]);
player.finish();
rendererFactory !.whenRenderingDone !().then(() => {
rendererFactory!.whenRenderingDone!().then(() => {
expect(eventLogs).toEqual(['void - start', 'void - done', 'on - start', 'on - done']);
done();
});

View File

@ -440,11 +440,12 @@ describe('styling', () => {
@Directive({selector: '[dir-shadows-class-input]'})
class DirectiveShadowsClassInput {
@Input('class')
klass: string|undefined;
@Input('class') klass: string|undefined;
@HostBinding('class')
get hostClasses() { return `${this.klass} SUFFIX`; }
get hostClasses() {
return `${this.klass} SUFFIX`;
}
}
TestBed.configureTestingModule({declarations: [Cmp, DirectiveShadowsClassInput]});
@ -671,7 +672,7 @@ describe('styling', () => {
it('should be able to bind zero', () => {
@Component({template: '<div #div [style.opacity]="opacity"></div>'})
class App {
@ViewChild('div') div !: ElementRef<HTMLElement>;
@ViewChild('div') div!: ElementRef<HTMLElement>;
opacity = 0;
}
@ -685,7 +686,7 @@ describe('styling', () => {
it('should be able to bind a SafeValue to backgroundImage', () => {
@Component({template: '<div [style.backgroundImage]="image"></div>'})
class Cmp {
image !: SafeStyle;
image!: SafeStyle;
}
TestBed.configureTestingModule({declarations: [Cmp]});
@ -724,7 +725,7 @@ describe('styling', () => {
it('should be able to bind a SafeValue to clip-path', () => {
@Component({template: '<div [style.clip-path]="path"></div>'})
class Cmp {
path !: SafeStyle;
path!: SafeStyle;
}
TestBed.configureTestingModule({declarations: [Cmp]});
@ -1011,15 +1012,15 @@ describe('styling', () => {
expect(capturedClassBindingCount).toEqual(1);
expect(capturedClassBindingValue as any).toEqual(null);
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
fixture.componentInstance.c = 'dynamic-value';
fixture.detectChanges();
expect(capturedClassBindingCount).toEqual(2);
expect(capturedClassBindingValue !).toEqual('dynamic-value');
expect(capturedClassBindingValue!).toEqual('dynamic-value');
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
fixture.componentInstance.c = null;
fixture.detectChanges();
@ -1027,7 +1028,7 @@ describe('styling', () => {
expect(capturedClassBindingCount).toEqual(3);
expect(capturedClassBindingValue as any).toEqual(null);
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
fixture.componentInstance.c = '';
fixture.detectChanges();
@ -1035,7 +1036,7 @@ describe('styling', () => {
expect(capturedClassBindingCount).toEqual(4);
expect(capturedClassBindingValue as any).toEqual('');
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
});
it('should write to [class] binding during `update` mode if there is an instantiation-level value',
@ -1069,7 +1070,7 @@ describe('styling', () => {
fixture.detectChanges();
expect(capturedClassBindingCount).toEqual(2);
expect(capturedClassBindingValue !).toEqual('dynamic-bar');
expect(capturedClassBindingValue!).toEqual('dynamic-bar');
});
it('should write to a `class` input binding if there is a static class value', () => {
@ -1102,9 +1103,9 @@ describe('styling', () => {
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
expect(capturedClassBindingValue !).toEqual('static-val');
expect(capturedClassBindingValue!).toEqual('static-val');
expect(capturedClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
expect(capturedMyClassBindingCount).toEqual(1);
});
@ -1195,19 +1196,19 @@ describe('styling', () => {
// would check if we possibly have this situation on every `advance()`
// instruction. We don't think this is worth it, and we are just going to live
// with this.
);
expect(capturedClassBindingValue !).toEqual('static-val');
);
expect(capturedClassBindingValue!).toEqual('static-val');
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
capturedClassBindingCount = 0;
fixture.componentInstance.c = 'dynamic-val';
fixture.detectChanges();
expect(capturedClassBindingCount).toEqual(1);
expect(capturedClassBindingValue !).toEqual('static-val dynamic-val');
expect(capturedClassBindingValue!).toEqual('static-val dynamic-val');
expect(capturedMyClassBindingCount).toEqual(1);
expect(capturedMyClassBindingValue !).toEqual('foo');
expect(capturedMyClassBindingValue!).toEqual('foo');
});
onlyInIvy('only ivy balances styling across directives and component host bindings')
@ -1240,7 +1241,6 @@ describe('styling', () => {
});
describe('NgClass', () => {
// We had a bug where NgClass would not allocate sufficient slots for host bindings,
// so it would overwrite information about other directives nearby. This test checks
// that TestDir's injector is not overwritten by NgClass, so TestDir should still
@ -1297,7 +1297,7 @@ describe('styling', () => {
template: '<span dir [classesInSchool]="classes" [styleOfClothing]="style"></span>',
})
class App {
@ViewChild(Dir) dir !: Dir;
@ViewChild(Dir) dir!: Dir;
classes = 'math';
style = '80s';
@ -1315,8 +1315,7 @@ describe('styling', () => {
it('should be able to bind to `className`', () => {
@Component({template: ''})
class App {
@HostBinding('className')
klass = 'one two';
@HostBinding('className') klass = 'one two';
}
TestBed.configureTestingModule({declarations: [App]});
@ -1471,8 +1470,7 @@ describe('styling', () => {
opacity: string|null = '0.5';
@ViewChild(CompWithStyling, {static: true})
compWithStyling: CompWithStyling|null = null;
@ViewChild(DirWithStyling, {static: true})
dirWithStyling: DirWithStyling|null = null;
@ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling|null = null;
}
TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]});
@ -1488,13 +1486,13 @@ describe('styling', () => {
expect(element.style.fontSize).toEqual('100px');
// once for the template flush and again for the host bindings
expect(ngDevMode !.rendererSetStyle).toEqual(4);
expect(ngDevMode!.rendererSetStyle).toEqual(4);
ngDevModeResetPerfCounters();
component.opacity = '0.6';
component.compWithStyling !.height = '100px';
component.compWithStyling !.width = '100px';
component.dirWithStyling !.fontSize = '50px';
component.compWithStyling!.height = '100px';
component.compWithStyling!.width = '100px';
component.dirWithStyling!.fontSize = '50px';
fixture.detectChanges();
expect(element.style.opacity).toEqual('0.6');
@ -1503,7 +1501,7 @@ describe('styling', () => {
expect(element.style.fontSize).toEqual('50px');
// once for the template flush and again for the host bindings
expect(ngDevMode !.rendererSetStyle).toEqual(4);
expect(ngDevMode!.rendererSetStyle).toEqual(4);
});
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
@ -1634,8 +1632,12 @@ describe('styling', () => {
public c: {[key: string]: any}|null = null;
updateClasses(classes: string) {
const c = this.c || (this.c = {});
Object.keys(this.c).forEach(className => { c[className] = false; });
classes.split(/\s+/).forEach(className => { c[className] = true; });
Object.keys(this.c).forEach(className => {
c[className] = false;
});
classes.split(/\s+/).forEach(className => {
c[className] = true;
});
}
public s: {[key: string]: any}|null = null;
@ -1694,8 +1696,7 @@ describe('styling', () => {
map: any = {width: '111px', opacity: '0.5'};
width: string|null|undefined = '555px';
@ViewChild('dir', {read: DirThatSetsStyling, static: true})
dir !: DirThatSetsStyling;
@ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling;
}
TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]});
@ -1763,8 +1764,7 @@ describe('styling', () => {
map: any = {width: '555px', height: '555px'};
@ViewChild('dir', {read: DirThatSetsStyling, static: true})
dir !: DirThatSetsStyling;
@ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling;
}
TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]});
@ -1991,7 +1991,7 @@ describe('styling', () => {
it('should be able to bind a SafeValue to clip-path', () => {
@Component({template: '<div [style.clip-path]="path"></div>'})
class Cmp {
path !: SafeStyle;
path!: SafeStyle;
}
TestBed.configureTestingModule({declarations: [Cmp]});
@ -2028,12 +2028,22 @@ describe('styling', () => {
public background: string = '1.png';
public color: string = 'red';
private getSafeStyle(value: string) { return this.sanitizer.bypassSecurityTrustStyle(value); }
private getSafeStyle(value: string) {
return this.sanitizer.bypassSecurityTrustStyle(value);
}
getBackgroundSafe() { return this.getSafeStyle(`url("/${this.background}")`); }
getWidthSafe() { return this.getSafeStyle(this.width); }
getHeightSafe() { return this.getSafeStyle(this.height); }
getColorUnsafe() { return this.color; }
getBackgroundSafe() {
return this.getSafeStyle(`url("/${this.background}")`);
}
getWidthSafe() {
return this.getSafeStyle(this.width);
}
getHeightSafe() {
return this.getSafeStyle(this.height);
}
getColorUnsafe() {
return this.color;
}
}
TestBed.configureTestingModule({
@ -2089,11 +2099,9 @@ describe('styling', () => {
class Cmp {
map: any = null;
@ViewChild('div', {read: DirWithStyleMap, static: true})
dir1 !: DirWithStyleMap;
@ViewChild('div', {read: DirWithStyleMap, static: true}) dir1!: DirWithStyleMap;
@ViewChild('div', {read: DirWithStyleMapPart2, static: true})
dir2 !: DirWithStyleMapPart2;
@ViewChild('div', {read: DirWithStyleMapPart2, static: true}) dir2!: DirWithStyleMapPart2;
}
TestBed.configureTestingModule(
@ -2352,8 +2360,8 @@ describe('styling', () => {
@Component({template: '<div #target one two></div>'})
class Cmp {
@ViewChild('target', {read: DirOne, static: true}) one !: DirOne;
@ViewChild('target', {read: DirTwo, static: true}) two !: DirTwo;
@ViewChild('target', {read: DirOne, static: true}) one!: DirOne;
@ViewChild('target', {read: DirTwo, static: true}) two!: DirTwo;
}
TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
@ -2443,7 +2451,9 @@ describe('styling', () => {
class Cmp {
s: any = {opacity: '1'};
clearStyle(): void { this.s = null; }
clearStyle(): void {
this.s = null;
}
}
TestBed.configureTestingModule({declarations: [Cmp]});
@ -2471,8 +2481,7 @@ describe('styling', () => {
class ChildCmp {
readyTpl = false;
@HostBinding('class.ready-host')
readyHost = false;
@HostBinding('class.ready-host') readyHost = false;
}
@Component({
@ -2490,10 +2499,9 @@ describe('styling', () => {
class ParentCmp {
private _prop = '';
@ViewChild('template', {read: ViewContainerRef})
vcr: ViewContainerRef = null !;
@ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!;
private child: ComponentRef<ChildCmp> = null !;
private child: ComponentRef<ChildCmp> = null!;
@Input()
set prop(value: string) {
@ -2505,7 +2513,9 @@ describe('styling', () => {
}
}
get prop() { return this._prop; }
get prop() {
return this._prop;
}
ngAfterViewInit() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ChildCmp);
@ -2559,8 +2569,7 @@ describe('styling', () => {
class ChildCmp {
readyTpl = false;
@HostBinding('class.ready-host')
readyHost = false;
@HostBinding('class.ready-host') readyHost = false;
}
@Component({
@ -2575,10 +2584,9 @@ describe('styling', () => {
class ParentCmp {
updateChild = false;
@ViewChild('template', {read: ViewContainerRef})
vcr: ViewContainerRef = null !;
@ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!;
private child: ComponentRef<ChildCmp> = null !;
private child: ComponentRef<ChildCmp> = null!;
ngDoCheck() {
if (this.updateChild) {
@ -2619,7 +2627,7 @@ describe('styling', () => {
expect(readyHost).toBeFalsy();
expect(readyChild).toBeFalsy();
const parent = fixture.componentInstance.parent !;
const parent = fixture.componentInstance.parent!;
parent.updateChild = true;
fixture.detectChanges(false);
@ -2675,7 +2683,9 @@ describe('styling', () => {
selector: '[dir]',
})
class Dir {
constructor(public elementRef: ElementRef, public renderer: Renderer2) { dirInstance = this; }
constructor(public elementRef: ElementRef, public renderer: Renderer2) {
dirInstance = this;
}
setStyles() {
const nativeEl = this.elementRef.nativeElement;
@ -2730,11 +2740,9 @@ describe('styling', () => {
() => {
@Directive({selector: '[blockStyles]'})
class StylesDirective {
@HostBinding('style.border')
border = '1px solid red';
@HostBinding('style.border') border = '1px solid red';
@HostBinding('style.background')
background = 'white';
@HostBinding('style.background') background = 'white';
}
@Component({
@ -2863,7 +2871,7 @@ describe('styling', () => {
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
fixture.detectChanges();
const div = fixture.nativeElement.querySelector('div') !;
const div = fixture.nativeElement.querySelector('div')!;
div.className += ' abc';
expect(splitSortJoin(div.className)).toEqual('abc');
@ -2877,7 +2885,9 @@ describe('styling', () => {
expect(splitSortJoin(div.className)).toEqual('4 5 6 7 abc');
function splitSortJoin(s: string) { return s.split(/\s+/).sort().join(' ').trim(); }
function splitSortJoin(s: string) {
return s.split(/\s+/).sort().join(' ').trim();
}
});
describe('ExpressionChangedAfterItHasBeenCheckedError', () => {
@ -2885,7 +2895,9 @@ describe('styling', () => {
@Component({template: `<div [style.background-image]="iconSafe"></div>`})
class MyComp {
icon = 'https://i.imgur.com/4AiXzf8.jpg';
get iconSafe() { return this.sanitizer.bypassSecurityTrustStyle(`url("${this.icon}")`); }
get iconSafe() {
return this.sanitizer.bypassSecurityTrustStyle(`url("${this.icon}")`);
}
constructor(private sanitizer: DomSanitizer) {}
}
@ -2951,7 +2963,7 @@ describe('styling', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirThatSetsStyling]})
.createComponent(MyComp);
fixture.detectChanges();
const div = fixture.nativeElement.querySelector('div') !;
const div = fixture.nativeElement.querySelector('div')!;
expect(div.style.getPropertyValue('width')).toEqual('100px');
expect(div.style.getPropertyValue('height')).toEqual('200px');
expect(div.style.getPropertyValue('font-size')).toEqual('300px');
@ -3051,20 +3063,16 @@ describe('styling', () => {
() => {
@Component({selector: 'my-comp-with-styling', template: ''})
class MyCompWithStyling {
@HostBinding('style')
myStyles: any = {width: '300px'};
@HostBinding('style') myStyles: any = {width: '300px'};
@HostBinding('style.height')
myHeight: any = '305px';
@HostBinding('style.height') myHeight: any = '305px';
}
@Directive({selector: '[my-dir-with-styling]'})
class MyDirWithStyling {
@HostBinding('style')
myStyles: any = {width: '200px'};
@HostBinding('style') myStyles: any = {width: '200px'};
@HostBinding('style.height')
myHeight: any = '205px';
@HostBinding('style.height') myHeight: any = '205px';
}
@Component({
@ -3081,15 +3089,15 @@ describe('styling', () => {
myStyles: {width?: string} = {width: '100px'};
myHeight: string|null|undefined = '100px';
@ViewChild(MyDirWithStyling) dir !: MyDirWithStyling;
@ViewChild(MyCompWithStyling) comp !: MyCompWithStyling;
@ViewChild(MyDirWithStyling) dir!: MyDirWithStyling;
@ViewChild(MyCompWithStyling) comp!: MyCompWithStyling;
}
TestBed.configureTestingModule(
{declarations: [MyComp, MyCompWithStyling, MyDirWithStyling]});
const fixture = TestBed.createComponent(MyComp);
const comp = fixture.componentInstance;
const elm = fixture.nativeElement.querySelector('my-comp-with-styling') !;
const elm = fixture.nativeElement.querySelector('my-comp-with-styling')!;
fixture.detectChanges();
expect(elm.style.width).toEqual('100px');
@ -3131,7 +3139,7 @@ describe('styling', () => {
TestBed.configureTestingModule(
{declarations: [MyComp, MyCompWithStyling, MyDirWithStyling]});
const fixture = TestBed.createComponent(MyComp);
const elm = fixture.nativeElement.querySelector('my-comp-with-styling') !;
const elm = fixture.nativeElement.querySelector('my-comp-with-styling')!;
fixture.detectChanges();
expect(elm.style.color).toEqual('red');
@ -3139,7 +3147,6 @@ describe('styling', () => {
it('should combine host class.foo bindings from multiple directives', () => {
@Directive({
selector: '[dir-that-sets-one-two]',
exportAs: 'one',
@ -3197,8 +3204,8 @@ describe('styling', () => {
expect(div2.className).toBe('');
const comp = fixture.componentInstance;
comp.dirOneA !.one = comp.dirOneB !.one = true;
comp.dirOneA !.two = comp.dirOneB !.two = true;
comp.dirOneA!.one = comp.dirOneB!.one = true;
comp.dirOneA!.two = comp.dirOneB!.two = true;
fixture.detectChanges();
expect(div1.classList.contains('one')).toBeTruthy();
@ -3211,8 +3218,8 @@ describe('styling', () => {
expect(div2.classList.contains('four')).toBeFalsy();
expect(div2.classList.contains('zero')).toBeFalsy();
comp.dirTwoA !.three = comp.dirTwoB !.three = true;
comp.dirTwoA !.four = comp.dirTwoB !.four = true;
comp.dirTwoA!.three = comp.dirTwoB!.three = true;
comp.dirTwoA!.four = comp.dirTwoB!.four = true;
fixture.detectChanges();
expect(div1.classList.contains('one')).toBeTruthy();
@ -3242,7 +3249,9 @@ describe('styling', () => {
it('should combine static host classes with component "class" host attribute', () => {
@Component({selector: 'comp-with-classes', template: '', host: {'class': 'host'}})
class CompWithClasses {
constructor(ref: ElementRef) { ref.nativeElement.classList.add('custom'); }
constructor(ref: ElementRef) {
ref.nativeElement.classList.add('custom');
}
}
@Component({
@ -3282,8 +3291,7 @@ describe('styling', () => {
@Directive({selector: '[single-host-style-dir]'})
class SingleHostStyleDir {
@HostBinding('style.width')
width = '100px';
@HostBinding('style.width') width = '100px';
}
TestBed.configureTestingModule({declarations: [Cmp, SingleHostStyleDir]});
@ -3340,7 +3348,9 @@ describe('styling', () => {
@Directive({selector: '[test]'})
class MyDir {
@Input('class')
set className(value: string) { logs.push(value); }
set className(value: string) {
logs.push(value);
}
}
@Component({
@ -3456,8 +3466,7 @@ describe('styling', () => {
template: `className = {{className}}`,
})
class MyCmp {
@Input()
className: string = 'unbound';
@Input() className: string = 'unbound';
}
@Component({template: `<my-cmp [class]="'bound'"></my-cmp>`})
class MyApp {
@ -3475,8 +3484,7 @@ describe('styling', () => {
template: `className = {{className}}`,
})
class MyCmp {
@Input()
className: string = 'unbound';
@Input() className: string = 'unbound';
}
@Component({template: `<my-cmp class="bound"></my-cmp>`})
class MyApp {
@ -3491,8 +3499,8 @@ describe('styling', () => {
});
function assertStyleCounters(countForSet: number, countForRemove: number) {
expect(ngDevMode !.rendererSetStyle).toEqual(countForSet);
expect(ngDevMode !.rendererRemoveStyle).toEqual(countForRemove);
expect(ngDevMode!.rendererSetStyle).toEqual(countForSet);
expect(ngDevMode!.rendererRemoveStyle).toEqual(countForRemove);
}
function assertStyle(element: HTMLElement, prop: string, value: any) {

View File

@ -13,11 +13,9 @@ import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
describe('TemplateRef', () => {
describe('rootNodes', () => {
@Component({template: `<ng-template #templateRef></ng-template>`})
class App {
@ViewChild('templateRef', {static: true})
templateRef !: TemplateRef<any>;
@ViewChild('templateRef', {static: true}) templateRef!: TemplateRef<any>;
minutes = 0;
}
@ -80,7 +78,7 @@ describe('TemplateRef', () => {
`
})
class App {
@ViewChild(MenuContent) content !: MenuContent;
@ViewChild(MenuContent) content!: MenuContent;
constructor(public viewContainerRef: ViewContainerRef) {}
}

View File

@ -7,7 +7,7 @@
*/
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {of } from 'rxjs';
import {of} from 'rxjs';
describe('text instructions', () => {
it('should handle all flavors of interpolated text', () => {
@ -66,8 +66,8 @@ describe('text instructions', () => {
`
})
class App {
who = of ('Sally');
item = of ({
who = of('Sally');
item = of({
what: 'seashells',
where: 'seashore',
});

View File

@ -8,7 +8,7 @@
import {CommonModule, DOCUMENT} from '@angular/common';
import {computeMsgId} from '@angular/compiler';
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, Injector, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, Injector, NgModule, NO_ERRORS_SCHEMA, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
import {Input} from '@angular/core/src/metadata';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {TestBed, TestComponentRenderer} from '@angular/core/testing';
@ -18,7 +18,6 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
describe('ViewContainerRef', () => {
/**
* Gets the inner HTML of the given element with all HTML comments and Angular internal
* reflect attributes omitted. This makes HTML comparisons easier and less verbose.
@ -40,7 +39,6 @@ describe('ViewContainerRef', () => {
afterEach(() => clearTranslations());
describe('create', () => {
it('should support view queries inside embedded views created in dir constructors', () => {
const fixture = TestBed.createComponent(ConstructorApp);
fixture.detectChanges();
@ -87,7 +85,7 @@ describe('ViewContainerRef', () => {
`
})
class TestComp {
@ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective;
@ViewChild(VCRefDirective, {static: true}) vcRefDir!: VCRefDirective;
}
TestBed.configureTestingModule(
@ -141,7 +139,7 @@ describe('ViewContainerRef', () => {
`
})
class TestComp {
@ViewChild('container', {read: ViewContainerRef}) vcRef !: ViewContainerRef;
@ViewChild('container', {read: ViewContainerRef}) vcRef!: ViewContainerRef;
constructor(public cfr: ComponentFactoryResolver) {}
@ -196,8 +194,8 @@ describe('ViewContainerRef', () => {
`
})
class TestComp {
@ViewChild('svg', {read: ViewContainerRef}) svgVCRef !: ViewContainerRef;
@ViewChild('mathml', {read: ViewContainerRef}) mathMLVCRef !: ViewContainerRef;
@ViewChild('svg', {read: ViewContainerRef}) svgVCRef!: ViewContainerRef;
@ViewChild('mathml', {read: ViewContainerRef}) mathMLVCRef!: ViewContainerRef;
constructor(public cfr: ComponentFactoryResolver) {}
@ -273,7 +271,7 @@ describe('ViewContainerRef', () => {
`
})
class TestComp {
@ViewChild('container', {read: ViewContainerRef}) vcRef !: ViewContainerRef;
@ViewChild('container', {read: ViewContainerRef}) vcRef!: ViewContainerRef;
private helloCompFactory = this.cfr.resolveComponentFactory(HelloComp);
@ -357,7 +355,9 @@ describe('ViewContainerRef', () => {
// Insert the view again at the same index
viewContainerRef.insert(ref0, 0);
expect(() => { fixture.destroy(); }).not.toThrow();
expect(() => {
fixture.destroy();
}).not.toThrow();
expect(fixture.nativeElement.textContent).toEqual('0');
});
@ -405,7 +405,6 @@ describe('ViewContainerRef', () => {
});
it('should insert a view already inserted into another container', () => {
@Component({
selector: 'test-cmpt',
template: `
@ -414,9 +413,9 @@ describe('ViewContainerRef', () => {
`
})
class TestComponent {
@ViewChild('t', {static: true}) t !: TemplateRef<{}>;
@ViewChild('c1', {static: true, read: ViewContainerRef}) c1 !: ViewContainerRef;
@ViewChild('c2', {static: true, read: ViewContainerRef}) c2 !: ViewContainerRef;
@ViewChild('t', {static: true}) t!: TemplateRef<{}>;
@ViewChild('c1', {static: true, read: ViewContainerRef}) c1!: ViewContainerRef;
@ViewChild('c2', {static: true, read: ViewContainerRef}) c2!: ViewContainerRef;
}
TestBed.configureTestingModule({declarations: [TestComponent]});
@ -660,7 +659,6 @@ describe('ViewContainerRef', () => {
});
describe('get and indexOf', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
});
@ -677,13 +675,13 @@ describe('ViewContainerRef', () => {
fixture.detectChanges();
let viewRef = vcRefDir.vcref.get(0);
expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(0);
expect(vcRefDir.vcref.indexOf(viewRef!)).toEqual(0);
viewRef = vcRefDir.vcref.get(1);
expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(1);
expect(vcRefDir.vcref.indexOf(viewRef!)).toEqual(1);
viewRef = vcRefDir.vcref.get(2);
expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(2);
expect(vcRefDir.vcref.indexOf(viewRef!)).toEqual(2);
});
it('should handle out of bounds cases', () => {
@ -700,7 +698,7 @@ describe('ViewContainerRef', () => {
const viewRef = vcRefDir.vcref.get(0);
vcRefDir.vcref.remove(0);
expect(vcRefDir.vcref.indexOf(viewRef !)).toEqual(-1);
expect(vcRefDir.vcref.indexOf(viewRef!)).toEqual(-1);
});
it('should return -1 as indexOf when no views were inserted', () => {
@ -742,21 +740,21 @@ describe('ViewContainerRef', () => {
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>**A**BC');
let viewRef = vcRefDir.vcref.get(0);
vcRefDir.vcref.move(viewRef !, 2);
vcRefDir.vcref.move(viewRef!, 2);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>BC**A**');
vcRefDir.vcref.move(viewRef !, 0);
vcRefDir.vcref.move(viewRef!, 0);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>**A**BC');
vcRefDir.vcref.move(viewRef !, 1);
vcRefDir.vcref.move(viewRef!, 1);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>B**A**C');
// Invalid indices when detaching throws an exception in Ivy: FW-1330.
ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef !, -1)).toThrow();
ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef !, 42)).toThrow();
ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef!, -1)).toThrow();
ivyEnabled && expect(() => vcRefDir.vcref.move(viewRef!, 42)).toThrow();
});
});
@ -769,7 +767,7 @@ describe('ViewContainerRef', () => {
`
})
class TestComponent {
@ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective;
@ViewChild(VCRefDirective, {static: true}) vcRefDir!: VCRefDirective;
}
TestBed.configureTestingModule({declarations: [VCRefDirective, TestComponent]});
@ -789,7 +787,6 @@ describe('ViewContainerRef', () => {
});
describe('detach', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [EmbeddedViewInsertionComp, VCRefDirective]});
@ -825,7 +822,7 @@ describe('ViewContainerRef', () => {
// Invalid indices when detaching throws an exception in Ivy: FW-1330.
ivyEnabled && expect(() => vcRefDir.vcref.detach(-1)).toThrow();
ivyEnabled && expect(() => vcRefDir.vcref.detach(42)).toThrow();
ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(0);
ivyEnabled && expect(ngDevMode!.rendererDestroyNode).toBe(0);
});
it('should detach the last embedded view when no index is specified', () => {
@ -846,7 +843,7 @@ describe('ViewContainerRef', () => {
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>ABCD');
expect(viewE.destroyed).toBeFalsy();
ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(0);
ivyEnabled && expect(ngDevMode!.rendererDestroyNode).toBe(0);
});
});
@ -895,7 +892,7 @@ describe('ViewContainerRef', () => {
// Invalid indices when detaching throws an exception in Ivy: FW-1330.
ivyEnabled && expect(() => vcRefDir.vcref.remove(-1)).toThrow();
ivyEnabled && expect(() => vcRefDir.vcref.remove(42)).toThrow();
ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(2);
ivyEnabled && expect(ngDevMode!.rendererDestroyNode).toBe(2);
});
it('should remove the last embedded view when no index is specified', () => {
@ -916,7 +913,7 @@ describe('ViewContainerRef', () => {
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement)).toEqual('<p vcref=""></p>ABCD');
expect(viewE.destroyed).toBeTruthy();
ivyEnabled && expect(ngDevMode !.rendererDestroyNode).toBe(1);
ivyEnabled && expect(ngDevMode!.rendererDestroyNode).toBe(1);
});
it('should throw when trying to insert a removed or destroyed view', () => {
@ -1063,7 +1060,7 @@ describe('ViewContainerRef', () => {
`
})
class TestComponent {
@ViewChild(VCRefDirective, {static: true}) vcRef !: VCRefDirective;
@ViewChild(VCRefDirective, {static: true}) vcRef!: VCRefDirective;
}
TestBed.configureTestingModule({declarations: [TestComponent, VCRefDirective]});
@ -1087,8 +1084,8 @@ describe('ViewContainerRef', () => {
expect(getElementHtml(fixture.nativeElement)).toEqual('YABC<footer></footer>');
// Invalid indices when detaching throws an exception in Ivy: FW-1330.
ivyEnabled && expect(() => vcRef !.createView('Z', -1)).toThrow();
ivyEnabled && expect(() => vcRef !.createView('Z', 5)).toThrow();
ivyEnabled && expect(() => vcRef!.createView('Z', -1)).toThrow();
ivyEnabled && expect(() => vcRef!.createView('Z', 5)).toThrow();
});
it('should apply directives and pipes of the host view to the TemplateRef', () => {
@ -1099,7 +1096,9 @@ describe('ViewContainerRef', () => {
@Pipe({name: 'starPipe'})
class StarPipe implements PipeTransform {
transform(value: any) { return `**${value}**`; }
transform(value: any) {
return `**${value}**`;
}
}
@Component({
@ -1121,15 +1120,14 @@ describe('ViewContainerRef', () => {
fixture.debugElement.query(By.directive(VCRefDirective)).injector.get(VCRefDirective);
fixture.detectChanges();
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement))
.toEqual(
'<child vcref="">**A**</child><child>**C**</child><child>**C**</child><child>**B**</child>');
});
});
describe('createComponent', () => {
@ -1140,9 +1138,13 @@ describe('ViewContainerRef', () => {
it('should work without Injector and NgModuleRef', () => {
@Component({selector: 'embedded-cmp', template: `foo`})
class EmbeddedComponent implements DoCheck, OnInit {
ngOnInit() { templateExecutionCounter++; }
ngOnInit() {
templateExecutionCounter++;
}
ngDoCheck() { templateExecutionCounter++; }
ngDoCheck() {
templateExecutionCounter++;
}
}
@NgModule({entryComponents: [EmbeddedComponent], declarations: [EmbeddedComponent]})
@ -1185,13 +1187,16 @@ describe('ViewContainerRef', () => {
selector: 'embedded-cmp',
template: `foo`,
})
class EmbeddedComponent implements DoCheck,
OnInit {
class EmbeddedComponent implements DoCheck, OnInit {
constructor(public s: String) {}
ngOnInit() { templateExecutionCounter++; }
ngOnInit() {
templateExecutionCounter++;
}
ngDoCheck() { templateExecutionCounter++; }
ngDoCheck() {
templateExecutionCounter++;
}
}
@NgModule({entryComponents: [EmbeddedComponent], declarations: [EmbeddedComponent]})
@ -1423,7 +1428,7 @@ describe('ViewContainerRef', () => {
const makeComponentFactory = (componentType: any) => ({
create: () => TestBed.createComponent(componentType).componentRef,
});
this.viewContainerRef !.createComponent(makeComponentFactory(Child) as any);
this.viewContainerRef!.createComponent(makeComponentFactory(Child) as any);
}
}
@ -1493,8 +1498,8 @@ describe('ViewContainerRef', () => {
`,
})
class LoopComp {
@Input() tpl !: TemplateRef<any>;
@Input() rows !: any[];
@Input() tpl!: TemplateRef<any>;
@Input() rows!: any[];
name = 'Loop';
}
@ -1715,7 +1720,6 @@ describe('ViewContainerRef', () => {
});
describe('lifecycle hooks', () => {
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
const log: string[] = [];
@ -1723,19 +1727,37 @@ describe('ViewContainerRef', () => {
class ComponentWithHooks {
@Input() name: string|undefined;
private log(msg: string) { log.push(msg); }
private log(msg: string) {
log.push(msg);
}
ngOnChanges() { this.log('onChanges-' + this.name); }
ngOnInit() { this.log('onInit-' + this.name); }
ngDoCheck() { this.log('doCheck-' + this.name); }
ngOnChanges() {
this.log('onChanges-' + this.name);
}
ngOnInit() {
this.log('onInit-' + this.name);
}
ngDoCheck() {
this.log('doCheck-' + this.name);
}
ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
ngAfterContentInit() {
this.log('afterContentInit-' + this.name);
}
ngAfterContentChecked() {
this.log('afterContentChecked-' + this.name);
}
ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
ngAfterViewInit() {
this.log('afterViewInit-' + this.name);
}
ngAfterViewChecked() {
this.log('afterViewChecked-' + this.name);
}
ngOnDestroy() { this.log('onDestroy-' + this.name); }
ngOnDestroy() {
this.log('onDestroy-' + this.name);
}
}
@NgModule({
@ -1784,7 +1806,7 @@ describe('ViewContainerRef', () => {
]);
log.length = 0;
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
expect(getElementHtml(fixture.nativeElement))
.toEqual('<hooks vcref="">A</hooks><hooks></hooks><hooks>B</hooks>');
expect(log).toEqual([]);
@ -1815,7 +1837,7 @@ describe('ViewContainerRef', () => {
]);
log.length = 0;
vcRefDir.vcref.insert(viewRef !);
vcRefDir.vcref.insert(viewRef!);
fixture.detectChanges();
expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
@ -1898,7 +1920,7 @@ describe('ViewContainerRef', () => {
]);
log.length = 0;
vcRefDir.vcref.insert(viewRef !);
vcRefDir.vcref.insert(viewRef!);
fixture.detectChanges();
expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
@ -1916,7 +1938,6 @@ describe('ViewContainerRef', () => {
});
describe('host bindings', () => {
it('should support host bindings on dynamically created components', () => {
@Component(
{selector: 'host-bindings', host: {'id': 'attribute', '[title]': 'title'}, template: ``})
@ -1926,7 +1947,7 @@ describe('ViewContainerRef', () => {
@Component({template: `<ng-template vcref></ng-template>`})
class TestComponent {
@ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective;
@ViewChild(VCRefDirective, {static: true}) vcRefDir!: VCRefDirective;
}
@NgModule({declarations: [HostBindingCmpt], entryComponents: [HostBindingCmpt]})
@ -1956,11 +1977,9 @@ describe('ViewContainerRef', () => {
expect(fixture.nativeElement.children[0].getAttribute('id')).toBe('attribute');
expect(fixture.nativeElement.children[0].getAttribute('title')).toBe('changed');
});
});
describe('projection', () => {
it('should project the ViewContainerRef content along its host, in an element', () => {
@Component({selector: 'child', template: '<div><ng-content></ng-content></div>'})
class Child {
@ -1990,7 +2009,7 @@ describe('ViewContainerRef', () => {
expect(getElementHtml(fixture.nativeElement))
.toEqual('<child><div><header vcref="">blah</header></div></child>');
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement))
.toEqual('<child><div><header vcref="">blah</header><span>bar</span></div></child>');
@ -2031,7 +2050,7 @@ describe('ViewContainerRef', () => {
.toEqual(
'<child-with-view>Before (inside)- Before projected <header vcref="">blah</header> After projected -After (inside)</child-with-view>');
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement))
@ -2067,7 +2086,6 @@ describe('ViewContainerRef', () => {
});
describe('with select', () => {
@Component({
selector: 'child-with-selector',
template: `
@ -2105,7 +2123,7 @@ describe('ViewContainerRef', () => {
.toEqual(
'<child-with-selector><p class="a"><header vcref="">blah</header></p><p class="b"></p></child-with-selector>');
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement))
@ -2132,13 +2150,13 @@ describe('ViewContainerRef', () => {
`
})
class MyComp {
@ViewChild('source', {static: true})
source !: TemplateRef<{}>;
@ViewChild('source', {static: true}) source!: TemplateRef<{}>;
@ViewChild('target', {read: ViewContainerRef, static: true})
target !: ViewContainerRef;
@ViewChild('target', {read: ViewContainerRef, static: true}) target!: ViewContainerRef;
ngOnInit() { this.target.createEmbeddedView(this.source); }
ngOnInit() {
this.target.createEmbeddedView(this.source);
}
}
TestBed.configureTestingModule({declarations: [MyComp, ContentComp]});
@ -2175,7 +2193,7 @@ describe('ViewContainerRef', () => {
.toEqual(
'<child-with-selector><p class="a"></p><p class="b"><footer vcref="">blah</footer></p></child-with-selector>');
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef !);
vcRefDir.vcref.createEmbeddedView(vcRefDir.tplRef!);
fixture.detectChanges();
expect(getElementHtml(fixture.nativeElement))
@ -2183,7 +2201,6 @@ describe('ViewContainerRef', () => {
'<child-with-selector><p class="a"></p><p class="b"><footer vcref="">blah</footer><span>bar</span></p></child-with-selector>');
});
});
});
describe('root view container ref', () => {
@ -2204,7 +2221,7 @@ describe('ViewContainerRef', () => {
containerEl = document.createElement('div');
document.body.appendChild(containerEl);
containerEl !.appendChild(rootEl);
containerEl!.appendChild(rootEl);
}
};
}
@ -2223,7 +2240,9 @@ describe('ViewContainerRef', () => {
class DynamicCompWithBindings implements DoCheck {
checkCount = 0;
ngDoCheck() { this.checkCount++; }
ngDoCheck() {
this.checkCount++;
}
}
@Component({template: ``})
@ -2248,21 +2267,21 @@ describe('ViewContainerRef', () => {
// Ivy inserts a comment for the root view container ref instance. This is not
// the case for view engine and we need to adjust the assertions.
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 2 : 1);
ivyEnabled && expect(containerEl !.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 2 : 1);
ivyEnabled && expect(containerEl!.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
expect((containerEl !.childNodes[0] as Element).tagName).toBe('DIV');
expect((containerEl!.childNodes[0] as Element).tagName).toBe('DIV');
vcRef.createComponent(cfResolver.resolveComponentFactory(DynamicCompWithBindings));
fixture.detectChanges();
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(containerEl !.childNodes[1].textContent).toBe('check count: 1');
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(containerEl!.childNodes[1].textContent).toBe('check count: 1');
fixture.detectChanges();
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(containerEl !.childNodes[1].textContent).toBe('check count: 2');
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(containerEl!.childNodes[1].textContent).toBe('check count: 2');
});
it('should create deep DOM tree immediately for dynamically created components', () => {
@ -2299,21 +2318,21 @@ describe('ViewContainerRef', () => {
// Ivy inserts a comment for the root view container ref instance. This is not
// the case for view engine and we need to adjust the assertions.
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 2 : 1);
ivyEnabled && expect(containerEl !.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 2 : 1);
ivyEnabled && expect(containerEl!.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);
expect((containerEl !.childNodes[0] as Element).tagName).toBe('DIV');
expect((containerEl!.childNodes[0] as Element).tagName).toBe('DIV');
vcRef.createComponent(cfResolver.resolveComponentFactory(DynamicCompWithChildren));
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(getElementHtml(containerEl !.childNodes[1] as Element))
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(getElementHtml(containerEl!.childNodes[1] as Element))
.toBe('<child><div></div></child>');
fixture.detectChanges();
expect(containerEl !.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(getElementHtml(containerEl !.childNodes[1] as Element))
expect(containerEl!.childNodes.length).toBe(ivyEnabled ? 3 : 2);
expect(getElementHtml(containerEl!.childNodes[1] as Element))
.toBe(`<child><div>text</div></child>`);
});
});
@ -2374,7 +2393,7 @@ class EmbeddedComponentWithNgZoneModule {
`
})
class ViewContainerRefComp {
@ViewChildren(TemplateRef) templates !: QueryList<TemplateRef<any>>;
@ViewChildren(TemplateRef) templates!: QueryList<TemplateRef<any>>;
constructor(public vcr: ViewContainerRef) {}
}
@ -2386,21 +2405,25 @@ class ViewContainerRefComp {
`
})
class ViewContainerRefApp {
@ViewChild(ViewContainerRefComp) vcrComp !: ViewContainerRefComp;
@ViewChild(ViewContainerRefComp) vcrComp!: ViewContainerRefComp;
}
@Directive({selector: '[structDir]'})
export class StructDir {
constructor(private vcref: ViewContainerRef, private tplRef: TemplateRef<any>) {}
create() { this.vcref.createEmbeddedView(this.tplRef); }
create() {
this.vcref.createEmbeddedView(this.tplRef);
}
destroy() { this.vcref.clear(); }
destroy() {
this.vcref.clear();
}
}
@Component({selector: 'destroy-cases', template: ` `})
class DestroyCasesComp {
@ViewChildren(StructDir) structDirs !: QueryList<StructDir>;
@ViewChildren(StructDir) structDirs!: QueryList<StructDir>;
}
@Directive({selector: '[constructorDir]'})
@ -2419,7 +2442,7 @@ class ConstructorDir {
`
})
class ConstructorApp {
@ViewChild('foo', {static: true}) foo !: ElementRef;
@ViewChild('foo', {static: true}) foo!: ElementRef;
}
@Component({
@ -2431,5 +2454,5 @@ class ConstructorApp {
`
})
class ConstructorAppWithQueries {
@ViewChild('foo', {static: true}) foo !: TemplateRef<any>;
@ViewChild('foo', {static: true}) foo!: TemplateRef<any>;
}

View File

@ -33,15 +33,14 @@ describe('view insertion', () => {
})
class App {
@ViewChild('container', {read: ViewContainerRef, static: true})
container: ViewContainerRef = null !;
container: ViewContainerRef = null!;
@ViewChild('simple', {read: TemplateRef, static: true})
simple: TemplateRef<any> = null !;
@ViewChild('simple', {read: TemplateRef, static: true}) simple: TemplateRef<any> = null!;
view0: EmbeddedViewRef<any> = null !;
view1: EmbeddedViewRef<any> = null !;
view2: EmbeddedViewRef<any> = null !;
view3: EmbeddedViewRef<any> = null !;
view0: EmbeddedViewRef<any> = null!;
view1: EmbeddedViewRef<any> = null!;
view2: EmbeddedViewRef<any> = null!;
view3: EmbeddedViewRef<any> = null!;
constructor(public changeDetector: ChangeDetectorRef) {}
@ -90,16 +89,14 @@ describe('view insertion', () => {
`
})
class App {
@ViewChild('container', {read: ViewContainerRef})
container: ViewContainerRef = null !;
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
@ViewChild('empty', {read: TemplateRef})
empty: TemplateRef<any> = null !;
@ViewChild('empty', {read: TemplateRef}) empty: TemplateRef<any> = null!;
view0: EmbeddedViewRef<any> = null !;
view1: EmbeddedViewRef<any> = null !;
view2: EmbeddedViewRef<any> = null !;
view3: EmbeddedViewRef<any> = null !;
view0: EmbeddedViewRef<any> = null!;
view1: EmbeddedViewRef<any> = null!;
view2: EmbeddedViewRef<any> = null!;
view3: EmbeddedViewRef<any> = null!;
ngAfterViewInit() {
// insert at the front
@ -140,16 +137,14 @@ describe('view insertion', () => {
`
})
class Comp {
@ViewChild('container', {read: ViewContainerRef})
container: ViewContainerRef = null !;
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
@ViewChild('projection', {read: TemplateRef})
projection: TemplateRef<any> = null !;
@ViewChild('projection', {read: TemplateRef}) projection: TemplateRef<any> = null!;
view0: EmbeddedViewRef<any> = null !;
view1: EmbeddedViewRef<any> = null !;
view2: EmbeddedViewRef<any> = null !;
view3: EmbeddedViewRef<any> = null !;
view0: EmbeddedViewRef<any> = null!;
view1: EmbeddedViewRef<any> = null!;
view2: EmbeddedViewRef<any> = null!;
view3: EmbeddedViewRef<any> = null!;
ngAfterViewInit() {
// insert at the front
@ -201,16 +196,14 @@ describe('view insertion', () => {
`
})
class App {
@ViewChild('container', {read: ViewContainerRef})
container: ViewContainerRef = null !;
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
@ViewChild('subContainer', {read: TemplateRef})
subContainer: TemplateRef<any> = null !;
@ViewChild('subContainer', {read: TemplateRef}) subContainer: TemplateRef<any> = null!;
view0: EmbeddedViewRef<any> = null !;
view1: EmbeddedViewRef<any> = null !;
view2: EmbeddedViewRef<any> = null !;
view3: EmbeddedViewRef<any> = null !;
view0: EmbeddedViewRef<any> = null!;
view1: EmbeddedViewRef<any> = null!;
view2: EmbeddedViewRef<any> = null!;
view3: EmbeddedViewRef<any> = null!;
constructor(public changeDetectorRef: ChangeDetectorRef) {}
@ -265,9 +258,9 @@ describe('view insertion', () => {
describe('before embedded view', () => {
@Component({selector: 'test-cmpt', template: ''})
class TestCmpt {
@ViewChild('before', {static: true}) beforeTpl !: TemplateRef<{}>;
@ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir;
@ViewChild('before', {static: true}) beforeTpl!: TemplateRef<{}>;
@ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir!: ViewInsertingDir;
minutes = 10;
@ -303,8 +296,9 @@ describe('view insertion', () => {
}
it('should insert before a view with the text node as the first root node',
() => { expect(createAndInsertViews('|before').textContent).toBe('insert|before'); });
it('should insert before a view with the text node as the first root node', () => {
expect(createAndInsertViews('|before').textContent).toBe('insert|before');
});
it('should insert before a view with the element as the first root node', () => {
expect(createAndInsertViews('<span>|before</span>').textContent).toBe('insert|before');
@ -336,13 +330,11 @@ describe('view insertion', () => {
it('should insert before a view with a container as the first root node', () => {
expect(createAndInsertViews(`<ng-template [ngIf]="true">|before</ng-template>`).textContent)
.toBe('insert|before');
});
it('should insert before a view with an empty container as the first root node', () => {
expect(createAndInsertViews(`<ng-template [ngIf]="true"></ng-template>|before`).textContent)
.toBe('insert|before');
});
onlyInIvy('VE incorrectly inserts views before ng-container content')
@ -353,7 +345,6 @@ describe('view insertion', () => {
<ng-template #after>|after</ng-template>
`).textContent)
.toBe('insert|before|after');
});
@ -363,7 +354,6 @@ describe('view insertion', () => {
<ng-template #after>|after</ng-template>
`).textContent)
.toBe('insert|before|after');
});
it('should insert before a view with an empty projection as the first root node', () => {
@ -414,11 +404,9 @@ describe('view insertion', () => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.textContent).toBe('start|testtest|end');
});
});
describe('before embedded view with projection', () => {
@Component({
selector: 'with-content',
template: `
@ -428,9 +416,9 @@ describe('view insertion', () => {
`
})
class WithContentCmpt {
@ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>;
@ViewChild('before', {static: true}) beforeTpl !: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir;
@ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
@ViewChild('before', {static: true}) beforeTpl!: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir!: ViewInsertingDir;
insert() {
const beforeView = this.beforeTpl.createEmbeddedView({});
@ -442,7 +430,7 @@ describe('view insertion', () => {
@Component({selector: 'test-cmpt', template: ''})
class TestCmpt {
@ViewChild('wc', {static: true}) withContentCmpt !: WithContentCmpt;
@ViewChild('wc', {static: true}) withContentCmpt!: WithContentCmpt;
}
beforeEach(() => {
@ -476,7 +464,6 @@ describe('view insertion', () => {
expect(fixture.nativeElement.textContent).toBe('insert|before');
});
});
describe('before component view', () => {
@ -503,8 +490,8 @@ describe('view insertion', () => {
`
})
class TestCmpt {
@ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir;
@ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir!: ViewInsertingDir;
constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {}
@ -537,16 +524,13 @@ describe('view insertion', () => {
expect(fixture.nativeElement.textContent).toBe('insert|before');
});
});
});
describe('non-regression', () => {
// https://github.com/angular/angular/issues/31971
it('should insert component views into ViewContainerRef injected by querying <ng-container>',
() => {
@Component({selector: 'dynamic-cmpt', template: 'dynamic'})
class DynamicComponent {
}
@ -562,8 +546,7 @@ describe('view insertion', () => {
`
})
class AppComponent {
@ViewChild('container', {read: ViewContainerRef, static: true})
vcr !: ViewContainerRef;
@ViewChild('container', {read: ViewContainerRef, static: true}) vcr!: ViewContainerRef;
constructor(private _cfr: ComponentFactoryResolver) {}
@ -595,7 +578,6 @@ describe('view insertion', () => {
// https://github.com/angular/angular/issues/33679
it('should insert embedded views into ViewContainerRef injected by querying <ng-container>',
() => {
@Component({
selector: 'app-root',
template: `
@ -608,12 +590,13 @@ describe('view insertion', () => {
`
})
class AppComponent {
@ViewChild('container', {read: ViewContainerRef, static: true})
vcr !: ViewContainerRef;
@ViewChild('container', {read: ViewContainerRef, static: true}) vcr!: ViewContainerRef;
@ViewChild('template', {read: TemplateRef, static: true}) template !: TemplateRef<any>;
click() { this.vcr.createEmbeddedView(this.template, undefined, 0); }
click() {
this.vcr.createEmbeddedView(this.template, undefined, 0);
}
}
TestBed.configureTestingModule({
@ -659,6 +642,5 @@ describe('view insertion', () => {
expect(fixture.nativeElement.textContent.trim()).toContain('2 1');
});
});
});

View File

@ -13,7 +13,6 @@ import {TestBed} from '@angular/core/testing';
describe('ViewRef', () => {
it('should remove nodes from DOM when the view is detached from app ref', () => {
@Component({selector: 'dynamic-cpt', template: '<div></div>'})
class DynamicComponent {
constructor(public elRef: ElementRef) {}
@ -21,7 +20,7 @@ describe('ViewRef', () => {
@Component({template: `<span></span>`})
class App {
componentRef !: ComponentRef<DynamicComponent>;
componentRef!: ComponentRef<DynamicComponent>;
constructor(
public appRef: ApplicationRef, private cfr: ComponentFactoryResolver,
private injector: Injector) {}
@ -33,7 +32,9 @@ describe('ViewRef', () => {
document.body.appendChild(this.componentRef.instance.elRef.nativeElement);
}
destroy() { (this.componentRef.hostView as InternalViewRef).detachFromAppRef(); }
destroy() {
(this.componentRef.hostView as InternalViewRef).detachFromAppRef();
}
}
@NgModule({declarations: [App, DynamicComponent], entryComponents: [DynamicComponent]})

File diff suppressed because it is too large Load Diff

View File

@ -10,376 +10,377 @@ import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser';
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
import {Component, HostBinding} from '@angular/core';
import {TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ActivatedRoute, Router, RouterOutlet} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
(function() {
// these tests are only mean't to be run within the DOM (for now)
if (isNode) return;
// these tests are only mean't to be run within the DOM (for now)
if (isNode) return;
describe('Animation Router Tests', function() {
function getLog(): MockAnimationPlayer[] {
return MockAnimationDriver.log as MockAnimationPlayer[];
}
describe('Animation Router Tests', function() {
function getLog(): MockAnimationPlayer[] {
return MockAnimationDriver.log as MockAnimationPlayer[];
}
function resetLog() { MockAnimationDriver.log = []; }
function resetLog() {
MockAnimationDriver.log = [];
}
beforeEach(() => {
resetLog();
TestBed.configureTestingModule({
imports: [RouterTestingModule, BrowserAnimationsModule],
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]
});
beforeEach(() => {
resetLog();
TestBed.configureTestingModule({
imports: [RouterTestingModule, BrowserAnimationsModule],
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]
});
});
it('should query the old and new routes via :leave and :enter', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':leave', animateChild()),
query(':enter', animateChild()),
]),
]),
],
template: `
it('should query the old and new routes via :leave and :enter', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':leave', animateChild()),
query(':enter', animateChild()),
]),
]),
],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet>
</div>
`
})
class ContainerCmp {
constructor(public router: Router) {}
})
class ContainerCmp {
constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
}
@Component({
selector: 'page1',
template: `page1`,
animations: [
trigger(
'page1Animation',
[
transition(
':leave',
[
style({width: '200px'}),
animate(1000, style({width: '0px'})),
]),
]),
]
})
class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true;
}
@Component({
selector: 'page1',
template: `page1`,
animations: [
trigger(
'page1Animation',
[
transition(
':leave',
[
style({width: '200px'}),
animate(1000, style({width: '0px'})),
]),
]),
]
})
class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true;
}
@Component({
selector: 'page2',
template: `page2`,
animations: [
trigger(
'page2Animation',
[
transition(
':enter',
[
style({opacity: 0}),
animate(1000, style({opacity: 1})),
]),
]),
]
})
class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true;
}
@Component({
selector: 'page2',
template: `page2`,
animations: [
trigger(
'page2Animation',
[
transition(
':enter',
[
style({opacity: 0}),
animate(1000, style({opacity: 1})),
]),
]),
]
})
class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true;
}
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
const player = engine.players[0] !;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
const player = engine.players[0]!;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(p1.duration).toEqual(1000);
expect(p1.keyframes).toEqual([
{offset: 0, width: '200px'},
{offset: 1, width: '0px'},
]);
expect(p1.duration).toEqual(1000);
expect(p1.keyframes).toEqual([
{offset: 0, width: '200px'},
{offset: 1, width: '0px'},
]);
expect(p2.duration).toEqual(2000);
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: .5, opacity: '0'},
{offset: 1, opacity: '1'},
]);
}));
expect(p2.duration).toEqual(2000);
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: .5, opacity: '0'},
{offset: 1, opacity: '1'},
]);
}));
it('should allow inner enter animations to be emulated within a routed item', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':enter', animateChild()),
]),
]),
],
template: `
it('should allow inner enter animations to be emulated within a routed item', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':enter', animateChild()),
]),
]),
],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet>
</div>
`
})
class ContainerCmp {
constructor(public router: Router) {}
})
class ContainerCmp {
constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
}
@Component({selector: 'page1', template: `page1`, animations: []})
class Page1Cmp {
}
@Component({selector: 'page1', template: `page1`, animations: []})
class Page1Cmp {
}
@Component({
selector: 'page2',
template: `
@Component({
selector: 'page2',
template: `
<h1>Page 2</h1>
<div *ngIf="exp" class="if-one" @ifAnimation></div>
<div *ngIf="exp" class="if-two" @ifAnimation></div>
`,
animations: [
trigger(
'page2Animation',
[
transition(
':enter',
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
]),
trigger(
'ifAnimation',
[transition(
':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])
]
})
class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true;
animations: [
trigger(
'page2Animation',
[
transition(
':enter',
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
]),
trigger(
'ifAnimation',
[transition(':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])
]
})
class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true;
public exp = true;
}
public exp = true;
}
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
const player = engine.players[0] !;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
const player = engine.players[0]!;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(p1.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: 1, opacity: '1'},
]);
expect(p1.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: 1, opacity: '1'},
]);
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: .5, opacity: '0'},
{offset: 1, opacity: '1'},
]);
}));
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'},
{offset: .5, opacity: '0'},
{offset: 1, opacity: '1'},
]);
}));
it('should allow inner leave animations to be emulated within a routed item', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':leave', animateChild()),
]),
]),
],
template: `
it('should allow inner leave animations to be emulated within a routed item', fakeAsync(() => {
@Component({
animations: [
trigger(
'routerAnimations',
[
transition(
'page1 => page2',
[
query(':leave', animateChild()),
]),
]),
],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet>
</div>
`
})
class ContainerCmp {
constructor(public router: Router) {}
})
class ContainerCmp {
constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null;
return value;
}
}
@Component({
selector: 'page1',
template: `
@Component({
selector: 'page1',
template: `
<h1>Page 1</h1>
<div *ngIf="exp" class="if-one" @ifAnimation></div>
<div *ngIf="exp" class="if-two" @ifAnimation></div>
`,
animations: [
trigger(
'page1Animation',
[
transition(
':leave',
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
]),
trigger(
'ifAnimation',
[transition(':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]),
]
})
class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true;
animations: [
trigger(
'page1Animation',
[
transition(
':leave',
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
]),
trigger(
'ifAnimation',
[transition(':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]),
]
})
class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true;
public exp = true;
}
public exp = true;
}
@Component({selector: 'page2', template: `page2`, animations: []})
class Page2Cmp {
}
@Component({selector: 'page2', template: `page2`, animations: []})
class Page2Cmp {
}
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
fixture.detectChanges();
engine.flush();
const player = engine.players[0] !;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
const player = engine.players[0]!;
const groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(p1.keyframes).toEqual([
{offset: 0, opacity: '1'},
{offset: 1, opacity: '0'},
]);
expect(p1.keyframes).toEqual([
{offset: 0, opacity: '1'},
{offset: 1, opacity: '0'},
]);
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '1'},
{offset: .5, opacity: '1'},
{offset: 1, opacity: '0'},
]);
}));
expect(p2.keyframes).toEqual([
{offset: 0, opacity: '1'},
{offset: .5, opacity: '1'},
{offset: 1, opacity: '0'},
]);
}));
it('should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries',
fakeAsync(() => {
@Component({
selector: 'ani-cmp',
animations: [
trigger(
'pageAnimation',
[
transition(
'page1 => page2',
[
query('.router-container :leave', animate('1s', style({opacity: 0}))),
query('.router-container :enter', animate('1s', style({opacity: 1}))),
]),
]),
],
template: `
it('should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries',
fakeAsync(() => {
@Component({
selector: 'ani-cmp',
animations: [
trigger(
'pageAnimation',
[
transition(
'page1 => page2',
[
query('.router-container :leave', animate('1s', style({opacity: 0}))),
query('.router-container :enter', animate('1s', style({opacity: 1}))),
]),
]),
],
template: `
<div [@pageAnimation]="prepRoute(outlet)">
<header>
<div class="inner">
@ -392,138 +393,144 @@ import {RouterTestingModule} from '@angular/router/testing';
</section>
</div>
`
})
class ContainerCmp {
loading = false;
})
class ContainerCmp {
loading = false;
constructor(public router: Router) {}
constructor(public router: Router) {}
prepRoute(outlet: any) { return outlet.activatedRouteData['animation']; }
prepRoute(outlet: any) {
return outlet.activatedRouteData['animation'];
}
}
@Component({selector: 'page1', template: `page1`})
class Page1Cmp {
}
@Component({selector: 'page2', template: `page2`})
class Page2Cmp {
}
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page1');
tick();
cmp.loading = true;
fixture.detectChanges();
engine.flush();
cmp.router.navigateByUrl('/page2');
tick();
cmp.loading = false;
fixture.detectChanges();
engine.flush();
const players = engine.players;
expect(players.length).toEqual(1);
const [p1] = players;
const innerPlayers =
((p1 as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer).players;
expect(innerPlayers.length).toEqual(2);
const [ip1, ip2] = innerPlayers as any;
expect(ip1.element.innerText).toEqual('page1');
expect(ip2.element.innerText).toEqual('page2');
}));
it('should allow a recursive set of :leave animations to occur for nested routes',
fakeAsync(() => {
@Component({selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
class ContainerCmp {
constructor(private _router: Router) {}
log: string[] = [];
enter() {
this._router.navigateByUrl('/(recur:recur/nested)');
}
@Component({selector: 'page1', template: `page1`})
class Page1Cmp {
leave() {
this._router.navigateByUrl('/');
}
}
@Component({selector: 'page2', template: `page2`})
class Page2Cmp {
@Component({
selector: 'recur-page',
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
animations: [
trigger(
'pageAnimations',
[
transition(':leave', [group([
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
query('@*', animateChild(), {optional: true})
])]),
]),
]
})
class RecurPageCmp {
@HostBinding('@pageAnimations') public animatePage = true;
@HostBinding('attr.data-depth') public depth = 0;
constructor(private container: ContainerCmp, private route: ActivatedRoute) {
this.route.data.subscribe(data => {
this.container.log.push(`DEPTH ${data.depth}`);
this.depth = data.depth;
});
}
}
TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])]
});
TestBed.configureTestingModule({
declarations: [ContainerCmp, RecurPageCmp],
imports: [RouterTestingModule.withRoutes([{
path: 'recur',
component: RecurPageCmp,
outlet: 'recur',
data: {depth: 0},
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
}])]
});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.router.initialNavigation();
tick();
fixture.detectChanges();
engine.flush();
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.enter();
tick();
fixture.detectChanges();
flushMicrotasks();
cmp.router.navigateByUrl('/page1');
tick();
cmp.loading = true;
fixture.detectChanges();
engine.flush();
expect(cmp.log).toEqual([
'DEPTH 0',
'DEPTH 1',
]);
cmp.router.navigateByUrl('/page2');
tick();
cmp.loading = false;
fixture.detectChanges();
engine.flush();
cmp.leave();
tick();
fixture.detectChanges();
const players = engine.players;
expect(players.length).toEqual(1);
const [p1] = players;
const players = getLog();
expect(players.length).toEqual(2);
const innerPlayers =
((p1 as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer).players;
expect(innerPlayers.length).toEqual(2);
const [ip1, ip2] = innerPlayers as any;
expect(ip1.element.innerText).toEqual('page1');
expect(ip2.element.innerText).toEqual('page2');
}));
it('should allow a recursive set of :leave animations to occur for nested routes',
fakeAsync(() => {
@Component({selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
class ContainerCmp {
constructor(private _router: Router) {}
log: string[] = [];
enter() { this._router.navigateByUrl('/(recur:recur/nested)'); }
leave() { this._router.navigateByUrl('/'); }
}
@Component({
selector: 'recur-page',
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
animations: [
trigger(
'pageAnimations',
[
transition(':leave', [group([
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
query('@*', animateChild(), {optional: true})
])]),
]),
]
})
class RecurPageCmp {
@HostBinding('@pageAnimations') public animatePage = true;
@HostBinding('attr.data-depth') public depth = 0;
constructor(private container: ContainerCmp, private route: ActivatedRoute) {
this.route.data.subscribe(data => {
this.container.log.push(`DEPTH ${data.depth}`);
this.depth = data.depth;
});
}
}
TestBed.configureTestingModule({
declarations: [ContainerCmp, RecurPageCmp],
imports: [RouterTestingModule.withRoutes([{
path: 'recur',
component: RecurPageCmp,
outlet: 'recur',
data: {depth: 0},
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
}])]
});
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.enter();
tick();
fixture.detectChanges();
flushMicrotasks();
expect(cmp.log).toEqual([
'DEPTH 0',
'DEPTH 1',
]);
cmp.leave();
tick();
fixture.detectChanges();
const players = getLog();
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(p1.element.getAttribute('data-depth')).toEqual('0');
expect(p2.element.getAttribute('data-depth')).toEqual('1');
}));
});
const [p1, p2] = players;
expect(p1.element.getAttribute('data-depth')).toEqual('0');
expect(p2.element.getAttribute('data-depth')).toEqual('1');
}));
});
});
function makeAnimationData(value: string, params: {[key: string]: any} = {}): {[key: string]: any} {

View File

@ -15,23 +15,22 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut
import {TestBed} from '../../testing';
(function() {
// these tests are only mean't to be run within the DOM (for now)
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
if (isNode) return;
// these tests are only mean't to be run within the DOM (for now)
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
if (isNode) return;
describe('animation integration tests using css keyframe animations', function() {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{provide: AnimationDriver, useClass: CssKeyframesDriver}],
imports: [BrowserAnimationsModule]
});
describe('animation integration tests using css keyframe animations', function() {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{provide: AnimationDriver, useClass: CssKeyframesDriver}],
imports: [BrowserAnimationsModule]
});
});
it('should compute (*) animation styles for a container that is being removed', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should compute (*) animation styles for a container that is being removed', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
@ -40,46 +39,46 @@ import {TestBed} from '../../testing';
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('* => *', animate(1000)),
])]
})
class Cmp {
public exp: boolean = false;
}
animations: [trigger(
'auto',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('* => *', animate(1000)),
])]
})
class Cmp {
public exp: boolean = false;
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
cmp.exp = true;
fixture.detectChanges();
expect(engine.players.length).toEqual(1);
let player = getPlayer(engine) as CssKeyframesPlayer;
expect(player.keyframes).toEqual([{height: '0px', offset: 0}, {height: '100px', offset: 1}]);
expect(engine.players.length).toEqual(1);
let player = getPlayer(engine) as CssKeyframesPlayer;
expect(player.keyframes).toEqual([{height: '0px', offset: 0}, {height: '100px', offset: 1}]);
player.finish();
if (browserDetection.isOldChrome) return;
player.finish();
if (browserDetection.isOldChrome) return;
cmp.exp = false;
fixture.detectChanges();
cmp.exp = false;
fixture.detectChanges();
player = getPlayer(engine) as CssKeyframesPlayer;
expect(player.keyframes).toEqual([{height: '100px', offset: 0}, {height: '0px', offset: 1}]);
});
player = getPlayer(engine) as CssKeyframesPlayer;
expect(player.keyframes).toEqual([{height: '100px', offset: 0}, {height: '0px', offset: 1}]);
});
it('should cleanup all existing @keyframe <style> objects after the animation has finished',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should cleanup all existing @keyframe <style> objects after the animation has finished',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div [@myAnimation]="myAnimationExp">
<div>1</div>
<div>2</div>
@ -88,278 +87,278 @@ import {TestBed} from '../../testing';
<div>5</div>
</div>
`,
animations: [trigger(
'myAnimation',
[
transition(
'* => go',
[
query(
'div',
[
style({opacity: 0}),
animate('1s', style({opacity: 0})),
]),
]),
])]
})
class Cmp {
public myAnimationExp = '';
}
animations: [trigger(
'myAnimation',
[
transition(
'* => go',
[
query(
'div',
[
style({opacity: 0}),
animate('1s', style({opacity: 0})),
]),
]),
])]
})
class Cmp {
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.myAnimationExp = 'go';
fixture.detectChanges();
cmp.myAnimationExp = 'go';
fixture.detectChanges();
const webPlayer = <AnimationGroupPlayer>getPlayer(engine);
const players = webPlayer.players as CssKeyframesPlayer[];
expect(players.length).toEqual(5);
const webPlayer = <AnimationGroupPlayer>getPlayer(engine);
const players = webPlayer.players as CssKeyframesPlayer[];
expect(players.length).toEqual(5);
const head = document.querySelector('head') !;
const sheets: any[] = [];
for (let i = 0; i < 5; i++) {
const sheet = findStyleObjectWithKeyframes(i);
expect(head.contains(sheet)).toBeTruthy();
sheets.push(sheet);
}
const head = document.querySelector('head')!;
const sheets: any[] = [];
for (let i = 0; i < 5; i++) {
const sheet = findStyleObjectWithKeyframes(i);
expect(head.contains(sheet)).toBeTruthy();
sheets.push(sheet);
}
cmp.myAnimationExp = 'go-back';
fixture.detectChanges();
cmp.myAnimationExp = 'go-back';
fixture.detectChanges();
for (let i = 0; i < 5; i++) {
expect(head.contains(sheets[i])).toBeFalsy();
}
});
for (let i = 0; i < 5; i++) {
expect(head.contains(sheets[i])).toBeFalsy();
}
});
it('should properly handle easing values that are apart of the sequence', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should properly handle easing values that are apart of the sequence', () => {
@Component({
selector: 'ani-cmp',
template: `
<div #elm [@myAnimation]="myAnimationExp"></div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
'* => goSteps',
[
style({opacity: 0}),
animate('1s ease-out', style({opacity: 1})),
]),
transition(
'* => goKeyframes',
[
animate('1s cubic-bezier(0.5, 1, 0.5, 1)', keyframes([
style({opacity: 0}),
style({opacity: 0.5}),
style({opacity: 1}),
])),
]),
]),
]
})
class Cmp {
@ViewChild('elm') public element: any;
animations: [
trigger(
'myAnimation',
[
transition(
'* => goSteps',
[
style({opacity: 0}),
animate('1s ease-out', style({opacity: 1})),
]),
transition(
'* => goKeyframes',
[
animate('1s cubic-bezier(0.5, 1, 0.5, 1)', keyframes([
style({opacity: 0}),
style({opacity: 0.5}),
style({opacity: 1}),
])),
]),
]),
]
})
class Cmp {
@ViewChild('elm') public element: any;
public myAnimationExp = '';
}
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.myAnimationExp = 'goSteps';
fixture.detectChanges();
cmp.myAnimationExp = 'goSteps';
fixture.detectChanges();
let kfElm = findStyleObjectWithKeyframes();
const [r1, r2] = kfElm.sheet.cssRules[0].cssRules;
assertEasing(r1, 'ease-out');
assertEasing(r2, '');
let kfElm = findStyleObjectWithKeyframes();
const [r1, r2] = kfElm.sheet.cssRules[0].cssRules;
assertEasing(r1, 'ease-out');
assertEasing(r2, '');
const element = cmp.element.nativeElement;
const element = cmp.element.nativeElement;
const webPlayer = getPlayer(engine);
cmp.myAnimationExp = 'goKeyframes';
fixture.detectChanges();
const webPlayer = getPlayer(engine);
cmp.myAnimationExp = 'goKeyframes';
fixture.detectChanges();
assertEasing(element, 'cubic-bezier(0.5,1,0.5,1)');
});
assertEasing(element, 'cubic-bezier(0.5,1,0.5,1)');
});
it('should restore existing style values once the animation completes', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should restore existing style values once the animation completes', () => {
@Component({
selector: 'ani-cmp',
template: `
<div #elm [@myAnimation]="myAnimationExp"></div>
`,
animations: [
trigger(
'myAnimation',
[
state('go', style({width: '200px'})),
transition(
'* => go',
[
style({height: '100px', width: '100px'}), group([
animate('1s', style({height: '200px'})),
animate('1s', style({width: '200px'}))
])
]),
]),
]
})
class Cmp {
@ViewChild('elm') public element: any;
animations: [
trigger(
'myAnimation',
[
state('go', style({width: '200px'})),
transition(
'* => go',
[
style({height: '100px', width: '100px'}), group([
animate('1s', style({height: '200px'})),
animate('1s', style({width: '200px'}))
])
]),
]),
]
})
class Cmp {
@ViewChild('elm') public element: any;
public myAnimationExp = '';
}
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
const element = cmp.element.nativeElement;
element.style['width'] = '50px';
element.style['height'] = '50px';
fixture.detectChanges();
const element = cmp.element.nativeElement;
element.style['width'] = '50px';
element.style['height'] = '50px';
assertStyle(element, 'width', '50px');
assertStyle(element, 'height', '50px');
assertStyle(element, 'width', '50px');
assertStyle(element, 'height', '50px');
cmp.myAnimationExp = 'go';
fixture.detectChanges();
cmp.myAnimationExp = 'go';
fixture.detectChanges();
const player = getPlayer(engine);
const player = getPlayer(engine);
assertStyle(element, 'width', '100px');
assertStyle(element, 'height', '100px');
assertStyle(element, 'width', '100px');
assertStyle(element, 'height', '100px');
player.finish();
player.finish();
assertStyle(element, 'width', '200px');
assertStyle(element, 'height', '50px');
});
assertStyle(element, 'width', '200px');
assertStyle(element, 'height', '50px');
});
it('should clean up 0 second animation styles (queried styles) that contain camel casing when complete',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should clean up 0 second animation styles (queried styles) that contain camel casing when complete',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div #elm [@myAnimation]="myAnimationExp">
<div class="foo"></div>
<div class="bar"></div>
</div>
`,
animations: [
trigger(
'myAnimation',
[
state('go', style({width: '200px'})),
transition(
'* => go',
[
query('.foo', [style({maxHeight: '0px'})]),
query(
'.bar',
[
style({width: '0px'}),
animate('1s', style({width: '100px'})),
]),
]),
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
animations: [
trigger(
'myAnimation',
[
state('go', style({width: '200px'})),
transition(
'* => go',
[
query('.foo', [style({maxHeight: '0px'})]),
query(
'.bar',
[
style({width: '0px'}),
animate('1s', style({width: '100px'})),
]),
]),
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
public myAnimationExp = '';
}
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const elm = cmp.element.nativeElement;
const foo = elm.querySelector('.foo') as HTMLElement;
const elm = cmp.element.nativeElement;
const foo = elm.querySelector('.foo') as HTMLElement;
cmp.myAnimationExp = 'go';
fixture.detectChanges();
cmp.myAnimationExp = 'go';
fixture.detectChanges();
expect(foo.style.getPropertyValue('max-height')).toEqual('0px');
expect(foo.style.getPropertyValue('max-height')).toEqual('0px');
const player = engine.players.pop() !;
player.finish();
const player = engine.players.pop()!;
player.finish();
expect(foo.style.getPropertyValue('max-height')).toBeFalsy();
});
expect(foo.style.getPropertyValue('max-height')).toBeFalsy();
});
it('should apply the `display` and `position` styles as regular inline styles for the duration of the animation',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should apply the `display` and `position` styles as regular inline styles for the duration of the animation',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div #elm [@myAnimation]="myAnimationExp" style="display:table; position:fixed"></div>
`,
animations: [
trigger(
'myAnimation',
[
state('go', style({display: 'inline-block'})),
transition(
'* => go',
[
style({display: 'inline', position: 'absolute', opacity: 0}),
animate('1s', style({display: 'inline', opacity: 1, position: 'static'})),
animate('1s', style({display: 'flexbox', opacity: 0})),
])
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
animations: [
trigger(
'myAnimation',
[
state('go', style({display: 'inline-block'})),
transition(
'* => go',
[
style({display: 'inline', position: 'absolute', opacity: 0}),
animate('1s', style({display: 'inline', opacity: 1, position: 'static'})),
animate('1s', style({display: 'flexbox', opacity: 0})),
])
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
public myAnimationExp = '';
}
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(AnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const elm = cmp.element.nativeElement;
expect(elm.style.getPropertyValue('display')).toEqual('table');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
const elm = cmp.element.nativeElement;
expect(elm.style.getPropertyValue('display')).toEqual('table');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
cmp.myAnimationExp = 'go';
fixture.detectChanges();
cmp.myAnimationExp = 'go';
fixture.detectChanges();
expect(elm.style.getPropertyValue('display')).toEqual('inline');
expect(elm.style.getPropertyValue('position')).toEqual('absolute');
expect(elm.style.getPropertyValue('display')).toEqual('inline');
expect(elm.style.getPropertyValue('position')).toEqual('absolute');
const player = engine.players.pop() !;
player.finish();
player.destroy();
const player = engine.players.pop()!;
player.finish();
player.destroy();
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
});
});
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
});
});
})();
function getPlayer(engine: AnimationEngine, index = 0) {
return (engine.players[index] as any) !.getRealPlayer();
return (engine.players[index] as any)!.getRealPlayer();
}
function findStyleObjectWithKeyframes(index?: number): any|null {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {animate, query, state, style, transition, trigger} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser';
import {AnimationDriver, ɵAnimationEngine, ɵsupportsWebAnimations, ɵWebAnimationsDriver, ɵWebAnimationsPlayer} from '@angular/animations/browser';
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_group_player';
import {Component, ViewChild} from '@angular/core';
@ -15,23 +15,22 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
(function() {
// these tests are only mean't to be run within the DOM (for now)
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
if (isNode || !ɵsupportsWebAnimations()) return;
// these tests are only mean't to be run within the DOM (for now)
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
if (isNode || !ɵsupportsWebAnimations()) return;
describe('animation integration tests using web animations', function() {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{provide: AnimationDriver, useClass: ɵWebAnimationsDriver}],
imports: [BrowserAnimationsModule]
});
describe('animation integration tests using web animations', function() {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{provide: AnimationDriver, useClass: ɵWebAnimationsDriver}],
imports: [BrowserAnimationsModule]
});
});
it('should compute (*) animation styles for a container that is being removed', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should compute (*) animation styles for a container that is being removed', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
@ -40,152 +39,147 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[
state('void', style({height: '0px'})), state('*', style({height: '*'})),
transition('* => *', animate(1000))
])]
})
class Cmp {
public exp: boolean = false;
}
animations: [trigger(
'auto',
[
state('void', style({height: '0px'})), state('*', style({height: '*'})),
transition('* => *', animate(1000))
])]
})
class Cmp {
public exp: boolean = false;
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
cmp.exp = true;
fixture.detectChanges();
expect(engine.players.length).toEqual(1);
let webPlayer =
(engine.players[0] as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(engine.players.length).toEqual(1);
let webPlayer =
(engine.players[0] as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '0px', offset: 0}, {height: '100px', offset: 1}
]);
expect(webPlayer.keyframes).toEqual([{height: '0px', offset: 0}, {height: '100px', offset: 1}]);
webPlayer.finish();
webPlayer.finish();
if (!browserDetection.isOldChrome) {
cmp.exp = false;
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
webPlayer = (engine.players[0] as TransitionAnimationPlayer)
.getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '0px', offset: 1}
]);
}
});
it('should compute (!) animation styles for a container that is being inserted', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
<div style="line-height:20px;">3</div>
<div style="line-height:20px;">4</div>
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[transition(
':enter', [style({height: '!'}), animate(1000, style({height: '120px'}))])])]
})
class Cmp {
public exp: boolean = false;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
if (!browserDetection.isOldChrome) {
cmp.exp = false;
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
let webPlayer =
webPlayer =
(engine.players[0] as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '120px', offset: 1}
{height: '100px', offset: 0}, {height: '0px', offset: 1}
]);
});
}
});
it('should compute pre (!) and post (*) animation styles with different dom states', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should compute (!) animation styles for a container that is being inserted', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
<div style="line-height:20px;">3</div>
<div style="line-height:20px;">4</div>
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[transition(':enter', [style({height: '!'}), animate(1000, style({height: '120px'}))])])]
})
class Cmp {
public exp: boolean = false;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
let webPlayer =
(engine.players[0] as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '120px', offset: 1}
]);
});
it('should compute pre (!) and post (*) animation styles with different dom states', () => {
@Component({
selector: 'ani-cmp',
template: `
<div [@myAnimation]="exp" #parent>
<div *ngFor="let item of items" class="child" style="line-height:20px">
- {{ item }}
</div>
</div>
`,
animations: [trigger(
'myAnimation',
[transition('* => *', [style({height: '!'}), animate(1000, style({height: '*'}))])])]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp !: number;
public items = [0, 1, 2, 3, 4];
}
animations: [trigger(
'myAnimation',
[transition('* => *', [style({height: '!'}), animate(1000, style({height: '*'}))])])]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp!: number;
public items = [0, 1, 2, 3, 4];
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = 1;
fixture.detectChanges();
engine.flush();
cmp.exp = 1;
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
let player = engine.players[0];
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(engine.players.length).toEqual(1);
let player = engine.players[0];
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '0px', offset: 0}, {height: '100px', offset: 1}
]);
expect(webPlayer.keyframes).toEqual([{height: '0px', offset: 0}, {height: '100px', offset: 1}]);
// we destroy the player because since it has started and is
// at 0ms duration a height value of `0px` will be extracted
// from the element and passed into the follow-up animation.
player.destroy();
// we destroy the player because since it has started and is
// at 0ms duration a height value of `0px` will be extracted
// from the element and passed into the follow-up animation.
player.destroy();
cmp.exp = 2;
cmp.items = [0, 1, 2, 6];
fixture.detectChanges();
engine.flush();
cmp.exp = 2;
cmp.items = [0, 1, 2, 6];
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
player = engine.players[0];
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(engine.players.length).toEqual(1);
player = engine.players[0];
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '80px', offset: 1}
]);
});
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '80px', offset: 1}
]);
});
it('should treat * styles as ! when a removal animation is being rendered', () => {
@Component({
selector: 'ani-cmp',
styles: [`
it('should treat * styles as ! when a removal animation is being rendered', () => {
@Component({
selector: 'ani-cmp',
styles: [`
.box {
width: 500px;
overflow:hidden;
@ -195,58 +189,60 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut
text-align:center;
}
`],
template: `
template: `
<button (click)="toggle()">Open / Close</button>
<hr />
<div *ngIf="exp" @slide class="box">
...
</div>
`,
animations: [trigger(
'slide',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('* => *', animate('500ms')),
])]
})
class Cmp {
exp = false;
animations: [trigger(
'slide',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('* => *', animate('500ms')),
])]
})
class Cmp {
exp = false;
toggle() { this.exp = !this.exp; }
toggle() {
this.exp = !this.exp;
}
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
cmp.exp = true;
fixture.detectChanges();
let player = engine.players[0] !;
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '0px', offset: 0},
{height: '300px', offset: 1},
]);
player.finish();
let player = engine.players[0]!;
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '0px', offset: 0},
{height: '300px', offset: 1},
]);
player.finish();
cmp.exp = false;
fixture.detectChanges();
cmp.exp = false;
fixture.detectChanges();
player = engine.players[0] !;
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '300px', offset: 0},
{height: '0px', offset: 1},
]);
});
player = engine.players[0]!;
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '300px', offset: 0},
{height: '0px', offset: 1},
]);
});
it('should treat * styles as ! for queried items that are collected in a container that is being removed',
() => {
@Component({
it('should treat * styles as ! for queried items that are collected in a container that is being removed',
() => {
@Component({
selector: 'my-app',
styles: [`
.list .outer {
@ -287,235 +283,236 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut
]
})
class Cmp {
items: any[] = [];
items: any[] = [];
get exp() { return this.items.length ? 'full' : 'empty'; }
empty() { this.items = []; }
full() { this.items = [0, 1, 2, 3, 4]; }
get exp() {
return this.items.length ? 'full' : 'empty';
}
TestBed.configureTestingModule({declarations: [Cmp]});
empty() {
this.items = [];
}
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
full() {
this.items = [0, 1, 2, 3, 4];
}
}
cmp.empty();
fixture.detectChanges();
let player = engine.players[0] !as TransitionAnimationPlayer;
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.empty();
fixture.detectChanges();
let player = engine.players[0]! as TransitionAnimationPlayer;
player.finish();
cmp.full();
fixture.detectChanges();
player = engine.players[0]! as TransitionAnimationPlayer;
let queriedPlayers =
((player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer).players;
expect(queriedPlayers.length).toEqual(5);
let i = 0;
for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([
{height: '0px', offset: 0},
{height: '50px', offset: 1},
]);
player.finish();
}
cmp.full();
fixture.detectChanges();
cmp.empty();
fixture.detectChanges();
player = engine.players[0] !as TransitionAnimationPlayer;
let queriedPlayers =
((player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer)
.players;
expect(queriedPlayers.length).toEqual(5);
player = engine.players[0]! as TransitionAnimationPlayer;
queriedPlayers =
((player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer).players;
expect(queriedPlayers.length).toEqual(5);
let i = 0;
for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([
{height: '0px', offset: 0},
{height: '50px', offset: 1},
]);
player.finish();
}
for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([
{height: '50px', offset: 0},
{height: '0px', offset: 1},
]);
}
});
cmp.empty();
fixture.detectChanges();
player = engine.players[0] !as TransitionAnimationPlayer;
queriedPlayers =
((player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer)
.players;
expect(queriedPlayers.length).toEqual(5);
for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([
{height: '50px', offset: 0},
{height: '0px', offset: 1},
]);
}
});
it('should compute intermediate styles properly when an animation is cancelled', () => {
@Component({
selector: 'ani-cmp',
template: `
it('should compute intermediate styles properly when an animation is cancelled', () => {
@Component({
selector: 'ani-cmp',
template: `
<div [@myAnimation]="exp">...</div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
'* => a',
[
style({width: 0, height: 0}),
animate('1s', style({width: '300px', height: '600px'})),
]),
transition('* => b', [animate('1s', style({opacity: 0}))]),
]),
]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp !: string;
}
animations: [
trigger(
'myAnimation',
[
transition(
'* => a',
[
style({width: 0, height: 0}),
animate('1s', style({width: '300px', height: '600px'})),
]),
transition('* => b', [animate('1s', style({opacity: 0}))]),
]),
]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp!: string;
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = 'a';
fixture.detectChanges();
cmp.exp = 'a';
fixture.detectChanges();
let player = engine.players[0] !;
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
webPlayer.setPosition(0.5);
let player = engine.players[0]!;
let webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
webPlayer.setPosition(0.5);
cmp.exp = 'b';
fixture.detectChanges();
cmp.exp = 'b';
fixture.detectChanges();
player = engine.players[0] !;
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(approximate(parseFloat(webPlayer.keyframes[0]['width'] as string), 150))
.toBeLessThan(0.05);
expect(approximate(parseFloat(webPlayer.keyframes[0]['height'] as string), 300))
.toBeLessThan(0.05);
});
player = engine.players[0]!;
webPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as ɵWebAnimationsPlayer;
expect(approximate(parseFloat(webPlayer.keyframes[0]['width'] as string), 150))
.toBeLessThan(0.05);
expect(approximate(parseFloat(webPlayer.keyframes[0]['height'] as string), 300))
.toBeLessThan(0.05);
});
it('should compute intermediate styles properly for multiple queried elements when an animation is cancelled',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should compute intermediate styles properly for multiple queried elements when an animation is cancelled',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div [@myAnimation]="exp">
<div *ngFor="let item of items" class="target"></div>
</div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
'* => full', [query(
'.target',
[
style({width: 0, height: 0}),
animate('1s', style({width: '500px', height: '1000px'})),
])]),
transition(
'* => empty', [query('.target', [animate('1s', style({opacity: 0}))])]),
]),
]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp !: string;
public items: any[] = [];
}
animations: [
trigger(
'myAnimation',
[
transition(
'* => full', [query(
'.target',
[
style({width: 0, height: 0}),
animate('1s', style({width: '500px', height: '1000px'})),
])]),
transition('* => empty', [query('.target', [animate('1s', style({opacity: 0}))])]),
]),
]
})
class Cmp {
// TODO(issue/24571): remove '!'.
public exp!: string;
public items: any[] = [];
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = 'full';
cmp.items = [0, 1, 2, 3, 4];
fixture.detectChanges();
cmp.exp = 'full';
cmp.items = [0, 1, 2, 3, 4];
fixture.detectChanges();
let player = engine.players[0] !;
let groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
let players = groupPlayer.players;
expect(players.length).toEqual(5);
let player = engine.players[0]!;
let groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
let players = groupPlayer.players;
expect(players.length).toEqual(5);
for (let i = 0; i < players.length; i++) {
const p = players[i] as ɵWebAnimationsPlayer;
p.setPosition(0.5);
}
for (let i = 0; i < players.length; i++) {
const p = players[i] as ɵWebAnimationsPlayer;
p.setPosition(0.5);
}
cmp.exp = 'empty';
cmp.items = [];
fixture.detectChanges();
cmp.exp = 'empty';
cmp.items = [];
fixture.detectChanges();
player = engine.players[0];
groupPlayer =
(player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
players = groupPlayer.players;
player = engine.players[0];
groupPlayer = (player as TransitionAnimationPlayer).getRealPlayer() as AnimationGroupPlayer;
players = groupPlayer.players;
expect(players.length).toEqual(5);
for (let i = 0; i < players.length; i++) {
const p = players[i] as ɵWebAnimationsPlayer;
expect(approximate(parseFloat(p.keyframes[0]['width'] as string), 250))
.toBeLessThan(0.05);
expect(approximate(parseFloat(p.keyframes[0]['height'] as string), 500))
.toBeLessThan(0.05);
}
});
expect(players.length).toEqual(5);
for (let i = 0; i < players.length; i++) {
const p = players[i] as ɵWebAnimationsPlayer;
expect(approximate(parseFloat(p.keyframes[0]['width'] as string), 250)).toBeLessThan(0.05);
expect(approximate(parseFloat(p.keyframes[0]['height'] as string), 500))
.toBeLessThan(0.05);
}
});
it('should apply the `display` and `position` styles as regular inline styles for the duration of the animation',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should apply the `display` and `position` styles as regular inline styles for the duration of the animation',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div #elm [@myAnimation]="myAnimationExp" style="display:table; position:fixed"></div>
`,
animations: [
trigger(
'myAnimation',
[
state('go', style({display: 'inline-block'})),
transition(
'* => go',
[
style({display: 'inline', position: 'absolute', opacity: 0}),
animate('1s', style({display: 'inline', opacity: 1, position: 'static'})),
animate('1s', style({display: 'flexbox', opacity: 0})),
])
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
animations: [
trigger(
'myAnimation',
[
state('go', style({display: 'inline-block'})),
transition(
'* => go',
[
style({display: 'inline', position: 'absolute', opacity: 0}),
animate('1s', style({display: 'inline', opacity: 1, position: 'static'})),
animate('1s', style({display: 'flexbox', opacity: 0})),
])
]),
]
})
class Cmp {
@ViewChild('elm', {static: true}) public element: any;
public myAnimationExp = '';
}
public myAnimationExp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
const elm = cmp.element.nativeElement;
expect(elm.style.getPropertyValue('display')).toEqual('table');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
const elm = cmp.element.nativeElement;
expect(elm.style.getPropertyValue('display')).toEqual('table');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
cmp.myAnimationExp = 'go';
fixture.detectChanges();
cmp.myAnimationExp = 'go';
fixture.detectChanges();
expect(elm.style.getPropertyValue('display')).toEqual('inline');
expect(elm.style.getPropertyValue('position')).toEqual('absolute');
expect(elm.style.getPropertyValue('display')).toEqual('inline');
expect(elm.style.getPropertyValue('position')).toEqual('absolute');
const player = engine.players.pop() !;
player.finish();
player.destroy();
const player = engine.players.pop()!;
player.finish();
player.destroy();
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
});
});
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
});
});
})();
function approximate(value: number, target: number) {

View File

@ -7,12 +7,12 @@
*/
import {Injector} from '@angular/core';
import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init';
import {TestBed, async, inject} from '../testing';
import {async, inject, TestBed} from '../testing';
{
describe('ApplicationInitStatus', () => {
describe('no initializers', () => {
it('should return true for `done`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
(status as any).runInitializers();
@ -22,7 +22,9 @@ import {TestBed, async, inject} from '../testing';
it('should return a promise that resolves immediately for `donePromise`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
(status as any).runInitializers();
status.donePromise.then(() => { expect(status.done).toBe(true); });
status.donePromise.then(() => {
expect(status.done).toBe(true);
});
})));
});
@ -34,10 +36,14 @@ import {TestBed, async, inject} from '../testing';
let initializerFactory = (injector: Injector) => {
return () => {
const initStatus = injector.get(ApplicationInitStatus);
initStatus.donePromise.then(() => { expect(completerResolver).toBe(true); });
initStatus.donePromise.then(() => {
expect(completerResolver).toBe(true);
});
};
};
promise = new Promise((res) => { resolve = res; });
promise = new Promise((res) => {
resolve = res;
});
TestBed.configureTestingModule({
providers: [
{provide: APP_INITIALIZER, multi: true, useValue: () => promise},

View File

@ -16,8 +16,9 @@ import {describe, expect, inject, it} from '../testing/src/testing_internal';
{
describe('Application module', () => {
it('should set the default locale to "en-US"',
inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); }));
it('should set the default locale to "en-US"', inject([LOCALE_ID], (defaultLocale: string) => {
expect(defaultLocale).toEqual('en-US');
}));
it('should set the default currency code to "USD"',
inject([DEFAULT_CURRENCY_CODE], (defaultCurrencyCode: string) => {

View File

@ -17,13 +17,16 @@ ivyEnabled && describe('ApplicationRef bootstrap', () => {
selector: 'hello-world',
template: '<div>Hello {{ name }}</div>',
})
class HelloWorldComponent implements OnInit,
DoCheck {
class HelloWorldComponent implements OnInit, DoCheck {
log: string[] = [];
name = 'World';
ngOnInit(): void { this.log.push('OnInit'); }
ngDoCheck(): void { this.log.push('DoCheck'); }
ngOnInit(): void {
this.log.push('OnInit');
}
ngDoCheck(): void {
this.log.push('DoCheck');
}
}
@NgModule({
@ -34,7 +37,7 @@ ivyEnabled && describe('ApplicationRef bootstrap', () => {
class MyAppModule {
}
it('should bootstrap hello world', withBody('<hello-world></hello-world>', async() => {
it('should bootstrap hello world', withBody('<hello-world></hello-world>', async () => {
const MyAppModuleFactory = new NgModuleFactory(MyAppModule);
const moduleRef =
await getTestBed().platform.bootstrapModuleFactory(MyAppModuleFactory, {ngZone: 'noop'});
@ -58,7 +61,7 @@ ivyEnabled && describe('ApplicationRef bootstrap', () => {
}));
it('should expose the `window.ng` global utilities',
withBody('<hello-world></hello-world>', async() => {
withBody('<hello-world></hello-world>', async () => {
const MyAppModuleFactory = new NgModuleFactory(MyAppModule);
const moduleRef =
await getTestBed().platform.bootstrapModuleFactory(MyAppModuleFactory, {ngZone: 'noop'});

View File

@ -19,7 +19,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
import {NoopNgZone} from '../src/zone/ng_zone';
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
import {async, ComponentFixtureNoNgZone, inject, TestBed, withModule} from '../testing';
@Component({selector: 'bootstrap-app', template: 'hello'})
class SomeComponent {
@ -29,7 +29,9 @@ class SomeComponent {
describe('bootstrap', () => {
let mockConsole: MockConsole;
beforeEach(() => { mockConsole = new MockConsole(); });
beforeEach(() => {
mockConsole = new MockConsole();
});
function createRootEl(selector = 'bootstrap-app') {
const doc = TestBed.inject(DOCUMENT);
@ -47,7 +49,7 @@ class SomeComponent {
function createModule(providers?: any[]): Type<any>;
function createModule(options: CreateModuleOptions): Type<any>;
function createModule(providersOrOptions: any[] | CreateModuleOptions | undefined): Type<any> {
function createModule(providersOrOptions: any[]|CreateModuleOptions|undefined): Type<any> {
let options: CreateModuleOptions = {};
if (Array.isArray(providersOrOptions)) {
options = {providers: providersOrOptions};
@ -98,8 +100,7 @@ class SomeComponent {
createRootEl();
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory);
// The component should see the child module providers
@ -128,8 +129,7 @@ class SomeComponent {
createRootEl('custom-selector');
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory, 'custom-selector');
// The component should see the child module providers
@ -137,7 +137,9 @@ class SomeComponent {
})));
describe('ApplicationRef', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
beforeEach(() => {
TestBed.configureTestingModule({imports: [createModule()]});
});
it('should throw when reentering tick', () => {
@Component({template: '{{reenter()}}'})
@ -175,7 +177,9 @@ class SomeComponent {
providers: [{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useValue: (compRef: any) => { capturedCompRefs.push(compRef); }
useValue: (compRef: any) => {
capturedCompRefs.push(compRef);
}
}]
});
});
@ -214,7 +218,9 @@ class SomeComponent {
it('should wait for asynchronous app initializers', async(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => { resolve = res; });
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
let initializerDone = false;
setTimeout(() => {
resolve(true);
@ -224,13 +230,20 @@ class SomeComponent {
defaultPlatform
.bootstrapModule(
createModule([{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]))
.then(_ => { expect(initializerDone).toBe(true); });
.then(_ => {
expect(initializerDone).toBe(true);
});
}));
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
defaultPlatform
.bootstrapModule(createModule(
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
.bootstrapModule(createModule([{
provide: APP_INITIALIZER,
useValue: () => {
throw 'Test';
},
multi: true
}]))
.then(() => expect(false).toBe(true), (e) => {
expect(e).toBe('Test');
// Error rethrown will be seen by the exception handler since it's after
@ -306,7 +319,7 @@ class SomeComponent {
});
}));
it('should resolve component resources when creating module factory', async() => {
it('should resolve component resources when creating module factory', async () => {
@Component({
selector: 'with-templates-app',
templateUrl: '/test-template.html',
@ -328,7 +341,7 @@ class SomeComponent {
});
onlyInIvy('We only need to define `LOCALE_ID` for runtime i18n')
.it('should define `LOCALE_ID`', async() => {
.it('should define `LOCALE_ID`', async () => {
@Component({
selector: 'i18n-app',
templateUrl: '',
@ -343,7 +356,7 @@ class SomeComponent {
expect(getLocaleId()).toEqual('ro');
});
it('should wait for APP_INITIALIZER to set providers for `LOCALE_ID`', async() => {
it('should wait for APP_INITIALIZER to set providers for `LOCALE_ID`', async () => {
let locale: string = '';
const testModule = createModule({
@ -365,7 +378,9 @@ class SomeComponent {
}));
it('should wait for asynchronous app initializers', async(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => { resolve = res; });
const promise: Promise<any> = new Promise((res) => {
resolve = res;
});
let initializerDone = false;
setTimeout(() => {
resolve(true);
@ -373,7 +388,7 @@ class SomeComponent {
}, 1);
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null) !;
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(
createModule([{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]));
defaultPlatform.bootstrapModuleFactory(moduleFactory).then(_ => {
@ -383,9 +398,14 @@ class SomeComponent {
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null) !;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule([{
provide: APP_INITIALIZER,
useValue: () => {
throw 'Test';
},
multi: true
}]));
expect(() => defaultPlatform.bootstrapModuleFactory(moduleFactory)).toThrow('Test');
// Error rethrown will be seen by the exception handler since it's after
// construction.
@ -395,7 +415,7 @@ class SomeComponent {
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
async(() => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null) !;
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
[{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}]));
defaultPlatform.bootstrapModuleFactory(moduleFactory)
@ -415,15 +435,13 @@ class SomeComponent {
@Component({template: '<ng-container #vc></ng-container>'})
class ContainerComp {
// TODO(issue/24571): remove '!'.
@ViewChild('vc', {read: ViewContainerRef})
vc !: ViewContainerRef;
@ViewChild('vc', {read: ViewContainerRef}) vc!: ViewContainerRef;
}
@Component({template: '<ng-template #t>Dynamic content</ng-template>'})
class EmbeddedViewComp {
// TODO(issue/24571): remove '!'.
@ViewChild(TemplateRef, {static: true})
tplRef !: TemplateRef<Object>;
@ViewChild(TemplateRef, {static: true}) tplRef!: TemplateRef<Object>;
}
beforeEach(() => {
@ -497,7 +515,7 @@ class SomeComponent {
vc.insert(hostView);
expect(() => appRef.attachView(hostView))
.toThrowError('This view is already attached to a ViewContainer!');
hostView = vc.detach(0) !;
hostView = vc.detach(0)!;
appRef.attachView(hostView);
expect(() => vc.insert(hostView))
@ -516,7 +534,9 @@ class SomeComponent {
class ClickComp {
text: string = '1';
onClick() { this.text += '1'; }
onClick() {
this.text += '1';
}
}
@Component({selector: 'micro-task-comp', template: `<span>{{text}}</span>`})
@ -524,7 +544,9 @@ class SomeComponent {
text: string = '1';
ngOnInit() {
Promise.resolve(null).then((_) => { this.text += '1'; });
Promise.resolve(null).then((_) => {
this.text += '1';
});
}
}
@ -533,7 +555,9 @@ class SomeComponent {
text: string = '1';
ngOnInit() {
setTimeout(() => { this.text += '1'; }, 10);
setTimeout(() => {
this.text += '1';
}, 10);
}
}
@ -544,7 +568,9 @@ class SomeComponent {
ngOnInit() {
Promise.resolve(null).then((_) => {
this.text += '1';
setTimeout(() => { this.text += '1'; }, 10);
setTimeout(() => {
this.text += '1';
}, 10);
});
}
}
@ -556,7 +582,9 @@ class SomeComponent {
ngOnInit() {
setTimeout(() => {
this.text += '1';
Promise.resolve(null).then((_: any) => { this.text += '1'; });
Promise.resolve(null).then((_: any) => {
this.text += '1';
});
}, 10);
}
}
@ -572,7 +600,9 @@ class SomeComponent {
});
});
afterEach(() => { expect(stableCalled).toBe(true, 'isStable did not emit true on stable'); });
afterEach(() => {
expect(stableCalled).toBe(true, 'isStable did not emit true on stable');
});
function expectStableTexts(component: Type<any>, expected: string[]) {
const fixture = TestBed.createComponent(component);
@ -593,26 +623,34 @@ class SomeComponent {
});
}
it('isStable should fire on synchronous component loading',
async(() => { expectStableTexts(SyncComp, ['1']); }));
it('isStable should fire on synchronous component loading', async(() => {
expectStableTexts(SyncComp, ['1']);
}));
it('isStable should fire after a microtask on init is completed',
async(() => { expectStableTexts(MicroTaskComp, ['11']); }));
it('isStable should fire after a microtask on init is completed', async(() => {
expectStableTexts(MicroTaskComp, ['11']);
}));
it('isStable should fire after a macrotask on init is completed',
async(() => { expectStableTexts(MacroTaskComp, ['11']); }));
it('isStable should fire after a macrotask on init is completed', async(() => {
expectStableTexts(MacroTaskComp, ['11']);
}));
it('isStable should fire only after chain of micro and macrotasks on init are completed',
async(() => { expectStableTexts(MicroMacroTaskComp, ['111']); }));
async(() => {
expectStableTexts(MicroMacroTaskComp, ['111']);
}));
it('isStable should fire only after chain of macro and microtasks on init are completed',
async(() => { expectStableTexts(MacroMicroTaskComp, ['111']); }));
async(() => {
expectStableTexts(MacroMicroTaskComp, ['111']);
}));
describe('unstable', () => {
let unstableCalled = false;
afterEach(
() => { expect(unstableCalled).toBe(true, 'isStable did not emit false on unstable'); });
afterEach(() => {
expect(unstableCalled).toBe(true, 'isStable did not emit false on unstable');
});
function expectUnstable(appRef: ApplicationRef) {
appRef.isStable.subscribe({

View File

@ -30,17 +30,20 @@ class MakeColorGreyDirective {
this._textColor = null;
}
toggle() { this._backgroundColor ? this.off() : this.on(); }
toggle() {
this._backgroundColor ? this.off() : this.on();
}
}
@Component({selector: 'box-with-overridden-styles', template: '...'})
class BoxWithOverriddenStylesComponent {
public active = false;
@HostBinding('style')
styles = {};
@HostBinding('style') styles = {};
constructor() { this.onInActive(); }
constructor() {
this.onInActive();
}
@HostListener('click', ['$event'])
toggle() {
@ -102,9 +105,13 @@ class AnimationWorldComponent {
private _hostElement: HTMLElement;
public styles: {[key: string]: any}|null = null;
constructor(element: ElementRef) { this._hostElement = element.nativeElement; }
constructor(element: ElementRef) {
this._hostElement = element.nativeElement;
}
makeClass(item: any) { return `record-${item.value}`; }
makeClass(item: any) {
return `record-${item.value}`;
}
toggleActive(item: any, makeColorGrey: MakeColorGreyDirective) {
item.active = !item.active;

View File

@ -17,10 +17,11 @@ const UTF8 = {
const PACKAGE = 'angular/packages/core/test/bundling/cyclic_import';
describe('treeshaking with uglify', () => {
let content: string;
const contentPath = require.resolve(path.join(PACKAGE, 'bundle.min_debug.js'));
beforeAll(() => { content = fs.readFileSync(contentPath, UTF8); });
beforeAll(() => {
content = fs.readFileSync(contentPath, UTF8);
});
describe('functional test in domino', () => {
it('should render hello world when not minified', withBody('<trigger></trigger>', () => {

View File

@ -17,21 +17,24 @@ const UTF8 = {
const PACKAGE = 'angular/packages/core/test/bundling/hello_world';
describe('treeshaking with uglify', () => {
let content: string;
const contentPath = require.resolve(path.join(PACKAGE, 'bundle.min_debug.js'));
beforeAll(() => { content = fs.readFileSync(contentPath, UTF8); });
beforeAll(() => {
content = fs.readFileSync(contentPath, UTF8);
});
it('should drop unused TypeScript helpers',
() => { expect(content).not.toContain('__asyncGenerator'); });
it('should drop unused TypeScript helpers', () => {
expect(content).not.toContain('__asyncGenerator');
});
it('should not contain rxjs from commonjs distro', () => {
expect(content).not.toContain('commonjsGlobal');
expect(content).not.toContain('createCommonjsModule');
});
it('should not contain zone.js',
() => { expect(content).not.toContain('global[\'Zone\'] = Zone'); });
it('should not contain zone.js', () => {
expect(content).not.toContain('global[\'Zone\'] = Zone');
});
describe('functional test in domino', () => {
it('should render hello world when not minified',

View File

@ -16,13 +16,15 @@ const UTF8 = {
const PACKAGE = 'angular/packages/core/test/bundling/hello_world_r2';
describe('treeshaking with uglify', () => {
let content: string;
const contentPath = require.resolve(path.join(PACKAGE, 'bundle.min_debug.js'));
beforeAll(() => { content = fs.readFileSync(contentPath, UTF8); });
beforeAll(() => {
content = fs.readFileSync(contentPath, UTF8);
});
it('should drop unused TypeScript helpers',
() => { expect(content).not.toContain('__asyncGenerator'); });
it('should drop unused TypeScript helpers', () => {
expect(content).not.toContain('__asyncGenerator');
});
it('should not contain rxjs from commonjs distro', () => {
expect(content).not.toContain('commonjsGlobal');

View File

@ -11,6 +11,7 @@ import {INJECTOR, ScopedService} from './usage';
describe('functional test for injection system bundling', () => {
it('should be able to inject the scoped service',
() => { expect(INJECTOR.get(ScopedService) instanceof ScopedService).toBe(true); });
it('should be able to inject the scoped service', () => {
expect(INJECTOR.get(ScopedService) instanceof ScopedService).toBe(true);
});
});

View File

@ -15,9 +15,13 @@ class Todo {
editing: boolean;
// TODO(issue/24571): remove '!'.
private _title !: string;
get title() { return this._title; }
set title(value: string) { this._title = value.trim(); }
private _title!: string;
get title() {
return this._title;
}
set title(value: string) {
this._title = value.trim();
}
constructor(title: string, public completed: boolean = false) {
this.editing = false;
@ -39,21 +43,37 @@ class TodoStore {
return this.todos.filter((todo: Todo) => todo.completed === completed);
}
allCompleted() { return this.todos.length === this.getCompleted().length; }
allCompleted() {
return this.todos.length === this.getCompleted().length;
}
setAllTo(completed: boolean) { this.todos.forEach((t: Todo) => t.completed = completed); }
setAllTo(completed: boolean) {
this.todos.forEach((t: Todo) => t.completed = completed);
}
removeCompleted() { this.todos = this.getWithCompleted(false); }
removeCompleted() {
this.todos = this.getWithCompleted(false);
}
getRemaining() { return this.getWithCompleted(false); }
getRemaining() {
return this.getWithCompleted(false);
}
getCompleted() { return this.getWithCompleted(true); }
getCompleted() {
return this.getWithCompleted(true);
}
toggleCompletion(todo: Todo) { todo.completed = !todo.completed; }
toggleCompletion(todo: Todo) {
todo.completed = !todo.completed;
}
remove(todo: Todo) { this.todos.splice(this.todos.indexOf(todo), 1); }
remove(todo: Todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
}
add(title: string) { this.todos.push(new Todo(title)); }
add(title: string) {
this.todos.push(new Todo(title));
}
}
@Component({

View File

@ -21,13 +21,13 @@ const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js'];
describe('functional test for todo', () => {
BUNDLES.forEach(bundle => {
describe(bundle, () => {
it('should render todo', withBody('<todo-app></todo-app>', async() => {
it('should render todo', withBody('<todo-app></todo-app>', async () => {
require(path.join(PACKAGE, bundle));
const toDoAppComponent = getComponent(document.querySelector('todo-app') !);
const toDoAppComponent = getComponent(document.querySelector('todo-app')!);
expect(document.body.textContent).toContain('todos');
expect(document.body.textContent).toContain('Demonstrate Components');
expect(document.body.textContent).toContain('4 items left');
document.querySelector('button') !.click();
document.querySelector('button')!.click();
await whenRendered(toDoAppComponent);
expect(document.body.textContent).toContain('3 items left');
}));

View File

@ -13,42 +13,64 @@ import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markD
class Todo {
editing: boolean;
get title() { return this._title; }
set title(value: string) { this._title = value.trim(); }
get title() {
return this._title;
}
set title(value: string) {
this._title = value.trim();
}
constructor(private _title: string, public completed: boolean = false) { this.editing = false; }
constructor(private _title: string, public completed: boolean = false) {
this.editing = false;
}
}
@Injectable({providedIn: 'root'})
class TodoStore {
todos: Array<Todo> = [
new Todo($localize `Demonstrate Components`),
new Todo($localize `Demonstrate Structural Directives`, true),
new Todo($localize`Demonstrate Components`),
new Todo($localize`Demonstrate Structural Directives`, true),
// Using a placeholder
new Todo($localize `Demonstrate ${'NgModules'}:value:`),
new Todo($localize `Demonstrate zoneless change detection`),
new Todo($localize `Demonstrate internationalization`),
new Todo($localize`Demonstrate ${'NgModules'}:value:`),
new Todo($localize`Demonstrate zoneless change detection`),
new Todo($localize`Demonstrate internationalization`),
];
private getWithCompleted(completed: boolean) {
return this.todos.filter((todo: Todo) => todo.completed === completed);
}
allCompleted() { return this.todos.length === this.getCompleted().length; }
allCompleted() {
return this.todos.length === this.getCompleted().length;
}
setAllTo(completed: boolean) { this.todos.forEach((t: Todo) => t.completed = completed); }
setAllTo(completed: boolean) {
this.todos.forEach((t: Todo) => t.completed = completed);
}
removeCompleted() { this.todos = this.getWithCompleted(false); }
removeCompleted() {
this.todos = this.getWithCompleted(false);
}
getRemaining() { return this.getWithCompleted(false); }
getRemaining() {
return this.getWithCompleted(false);
}
getCompleted() { return this.getWithCompleted(true); }
getCompleted() {
return this.getWithCompleted(true);
}
toggleCompletion(todo: Todo) { todo.completed = !todo.completed; }
toggleCompletion(todo: Todo) {
todo.completed = !todo.completed;
}
remove(todo: Todo) { this.todos.splice(this.todos.indexOf(todo), 1); }
remove(todo: Todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
}
add(title: string) { this.todos.push(new Todo(title)); }
add(title: string) {
this.todos.push(new Todo(title));
}
}
@Component({

View File

@ -20,17 +20,17 @@ const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js'];
describe('functional test for todo i18n', () => {
BUNDLES.forEach(bundle => {
describe(bundle, () => {
it('should render todo i18n', withBody('<todo-app></todo-app>', async() => {
it('should render todo i18n', withBody('<todo-app></todo-app>', async () => {
clearTranslations();
require(path.join(PACKAGE, bundle));
const toDoAppComponent = getComponent(document.querySelector('todo-app') !);
const toDoAppComponent = getComponent(document.querySelector('todo-app')!);
expect(document.body.textContent).toContain('liste de tâches');
expect(document.body.textContent).toContain('Démontrer les components');
expect(document.body.textContent).toContain('Démontrer NgModules');
expect(document.body.textContent).toContain('4 tâches restantes');
expect(document.querySelector('.new-todo') !.getAttribute('placeholder'))
expect(document.querySelector('.new-todo')!.getAttribute('placeholder'))
.toEqual(`Qu'y a-t-il à faire ?`);
document.querySelector('button') !.click();
document.querySelector('button')!.click();
await whenRendered(toDoAppComponent);
expect(document.body.textContent).toContain('3 tâches restantes');
}));

View File

@ -16,9 +16,13 @@ class Todo {
editing: boolean;
// TODO(issue/24571): remove '!'.
private _title !: string;
get title() { return this._title; }
set title(value: string) { this._title = value.trim(); }
private _title!: string;
get title() {
return this._title;
}
set title(value: string) {
this._title = value.trim();
}
constructor(title: string, public completed: boolean = false) {
this.editing = false;
@ -40,21 +44,37 @@ class TodoStore {
return this.todos.filter((todo: Todo) => todo.completed === completed);
}
allCompleted() { return this.todos.length === this.getCompleted().length; }
allCompleted() {
return this.todos.length === this.getCompleted().length;
}
setAllTo(completed: boolean) { this.todos.forEach((t: Todo) => t.completed = completed); }
setAllTo(completed: boolean) {
this.todos.forEach((t: Todo) => t.completed = completed);
}
removeCompleted() { this.todos = this.getWithCompleted(false); }
removeCompleted() {
this.todos = this.getWithCompleted(false);
}
getRemaining() { return this.getWithCompleted(false); }
getRemaining() {
return this.getWithCompleted(false);
}
getCompleted() { return this.getWithCompleted(true); }
getCompleted() {
return this.getWithCompleted(true);
}
toggleCompletion(todo: Todo) { todo.completed = !todo.completed; }
toggleCompletion(todo: Todo) {
todo.completed = !todo.completed;
}
remove(todo: Todo) { this.todos.splice(this.todos.indexOf(todo), 1); }
remove(todo: Todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
}
add(title: string) { this.todos.push(new Todo(title)); }
add(title: string) {
this.todos.push(new Todo(title));
}
}
@Component({
@ -120,14 +140,18 @@ class TodoStore {
class ToDoAppComponent {
newTodoText = '';
constructor(public todoStore: TodoStore) { (window as any).toDoAppComponent = this; }
constructor(public todoStore: TodoStore) {
(window as any).toDoAppComponent = this;
}
stopEditing(todo: Todo, editedTitle: string) {
todo.title = editedTitle;
todo.editing = false;
}
cancelEditingTodo(todo: Todo) { todo.editing = false; }
cancelEditingTodo(todo: Todo) {
todo.editing = false;
}
updateEditingTodo(todo: Todo, editedTitle: string) {
editedTitle = editedTitle.trim();
@ -140,13 +164,21 @@ class ToDoAppComponent {
todo.title = editedTitle;
}
editTodo(todo: Todo) { todo.editing = true; }
editTodo(todo: Todo) {
todo.editing = true;
}
removeCompleted() { this.todoStore.removeCompleted(); }
removeCompleted() {
this.todoStore.removeCompleted();
}
toggleCompletion(todo: Todo) { this.todoStore.toggleCompletion(todo); }
toggleCompletion(todo: Todo) {
this.todoStore.toggleCompletion(todo);
}
remove(todo: Todo) { this.todoStore.remove(todo); }
remove(todo: Todo) {
this.todoStore.remove(todo);
}
addTodo() {
if (this.newTodoText.trim().length) {
@ -158,7 +190,9 @@ class ToDoAppComponent {
@NgModule({declarations: [ToDoAppComponent], imports: [CommonModule, BrowserModule]})
class ToDoAppModule {
ngDoBootstrap(app: any) { app.bootstrap(ToDoAppComponent); }
ngDoBootstrap(app: any) {
app.bootstrap(ToDoAppComponent);
}
}
(window as any).waitForApp =

View File

@ -21,9 +21,9 @@ describe('functional test for todo', () => {
BUNDLES.forEach(bundle => {
describe(bundle, () => {
it('should place styles on the elements within the component',
withBody('<todo-app></todo-app>', async() => {
withBody('<todo-app></todo-app>', async () => {
require(path.join(PACKAGE, bundle));
await(window as any).waitForApp;
await (window as any).waitForApp;
const toDoAppComponent = (window as any).toDoAppComponent;
await whenRendered(toDoAppComponent);

View File

@ -49,8 +49,9 @@ import {devModeEqual} from '@angular/core/src/change_detection/change_detection_
expect(devModeEqual(null, {})).toBe(false);
});
it('should return true for other objects',
() => { expect(devModeEqual({}, {})).toBe(true); });
it('should return true for other objects', () => {
expect(devModeEqual({}, {})).toBe(true);
});
});
});
}

View File

@ -14,13 +14,17 @@ import {iterableChangesAsString, iterableDifferToString} from '../../change_dete
class ItemWithId {
constructor(private id: string) {}
toString() { return `{id: ${this.id}}`; }
toString() {
return `{id: ${this.id}}`;
}
}
class ComplexItem {
constructor(private id: string, private color: string) {}
toString() { return `{id: ${this.id}, color: ${this.color}}`; }
toString() {
return `{id: ${this.id}, color: ${this.color}}`;
}
}
// TODO(vicb): UnmodifiableListView / frozen object when implemented
@ -29,7 +33,9 @@ class ComplexItem {
describe('DefaultIterableDiffer', function() {
let differ: DefaultIterableDiffer<any>;
beforeEach(() => { differ = new DefaultIterableDiffer(); });
beforeEach(() => {
differ = new DefaultIterableDiffer();
});
it('should support list and iterables', () => {
const f = new DefaultIterableDifferFactory();
@ -47,10 +53,9 @@ class ComplexItem {
l.list = [1];
differ.check(l);
expect(iterableDifferToString(differ)).toEqual(iterableChangesAsString({
collection: ['1[null->0]'],
additions: ['1[null->0]']
}));
expect(iterableDifferToString(differ))
.toEqual(
iterableChangesAsString({collection: ['1[null->0]'], additions: ['1[null->0]']}));
l.list = [2, 1];
differ.check(l);
@ -69,10 +74,9 @@ class ComplexItem {
l.push('a');
differ.check(l);
expect(iterableDifferToString(differ)).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
expect(iterableDifferToString(differ))
.toEqual(
iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']}));
l.push('b');
differ.check(l);
@ -148,10 +152,9 @@ class ComplexItem {
l.push('a');
differ.check(l);
expect(iterableDifferToString(differ)).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
expect(iterableDifferToString(differ))
.toEqual(
iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']}));
l.push('b');
differ.check(l);
@ -299,7 +302,7 @@ class ComplexItem {
describe('forEachOperation', () => {
function stringifyItemChange(
record: any, p: number | null, c: number | null, originalIndex: number) {
record: any, p: number|null, c: number|null, originalIndex: number) {
const suffix = originalIndex == null ? '' : ' [o=' + originalIndex + ']';
const value = record.item;
if (record.currentIndex == null) {
@ -312,13 +315,13 @@ class ComplexItem {
}
function modifyArrayUsingOperation(
arr: number[], endData: any[], prev: number | null, next: number | null) {
let value: number = null !;
arr: number[], endData: any[], prev: number|null, next: number|null) {
let value: number = null!;
if (prev == null) {
// "next" index is guaranteed to be set since the previous index is
// not defined and therefore a new entry is added.
value = endData[next !];
arr.splice(next !, 0, value);
value = endData[next!];
arr.splice(next!, 0, value);
} else if (next == null) {
value = arr[prev];
arr.splice(prev, 1);
@ -335,11 +338,11 @@ class ComplexItem {
const startData = [0, 1, 2, 3, 4, 5];
const endData = [6, 2, 7, 0, 4, 8];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
const value = modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -358,11 +361,11 @@ class ComplexItem {
const startData = [0, 1, 2, 3];
const endData = [2, 1];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -378,11 +381,11 @@ class ComplexItem {
const startData = [1, 2, 3, 4, 5, 6];
const endData = [3, 6, 4, 9, 1, 2];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -399,11 +402,11 @@ class ComplexItem {
const startData = [0, 1, 2, 3, 4];
const endData = [4, 1, 2, 3, 0, 5];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -420,11 +423,11 @@ class ComplexItem {
const startData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
const endData = [10, 11, 1, 5, 7, 8, 0, 5, 3, 6];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -446,11 +449,11 @@ class ComplexItem {
const startData = [1, 2, 3, 4];
const endData = [5, 6, 7, 8];
differ = differ.diff(startData) !;
differ = differ.diff(endData) !;
differ = differ.diff(startData)!;
differ = differ.diff(endData)!;
const operations: string[] = [];
differ.forEachOperation((item: any, prev: number | null, next: number | null) => {
differ.forEachOperation((item: any, prev: number|null, next: number|null) => {
const value = modifyArrayUsingOperation(startData, endData, prev, next);
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
});
@ -471,7 +474,7 @@ class ComplexItem {
it('should treat null as an empty list', () => {
differ.diff(['a', 'b']);
expect(iterableDifferToString(differ.diff(null !) !)).toEqual(iterableChangesAsString({
expect(iterableDifferToString(differ.diff(null!)!)).toEqual(iterableChangesAsString({
previous: ['a[0->null]', 'b[1->null]'],
removals: ['a[0->null]', 'b[1->null]']
}));
@ -490,7 +493,9 @@ class ComplexItem {
const buildItemList = (list: string[]) => list.map((val) => new ItemWithId(val));
beforeEach(() => { differ = new DefaultIterableDiffer(trackByItemId); });
beforeEach(() => {
differ = new DefaultIterableDiffer(trackByItemId);
});
it('should treat the collection as dirty if identity changes', () => {
differ.diff(buildItemList(['a']));
@ -539,7 +544,6 @@ class ComplexItem {
previous: ['{id: a}[0->1]', '{id: b}[1->0]', '{id: c}'],
moves: ['{id: b}[1->0]', '{id: a}[0->1]']
}));
});
it('should track duplicate reinsertion normally', () => {
@ -555,7 +559,6 @@ class ComplexItem {
moves: ['{id: a}[0->1]', '{id: a}[1->2]'],
additions: ['{id: b}[null->0]']
}));
});
it('should track removals normally', () => {
@ -577,7 +580,9 @@ class ComplexItem {
const trackByIndex = (index: number, item: any): number => index;
beforeEach(() => { differ = new DefaultIterableDiffer(trackByIndex); });
beforeEach(() => {
differ = new DefaultIterableDiffer(trackByIndex);
});
it('should track removals normally', () => {
differ.check(['a', 'b', 'c', 'd']);

View File

@ -23,7 +23,9 @@ import {kvChangesAsString, testChangesAsString} from '../../change_detection/uti
m = new Map();
});
afterEach(() => { differ = null !; });
afterEach(() => {
differ = null!;
});
it('should detect additions', () => {
differ.check(m);
@ -66,7 +68,6 @@ import {kvChangesAsString, testChangesAsString} from '../../change_detection/uti
expect(record.previousValue).toEqual(10);
expect(record.currentValue).toEqual(20);
});
});
it('should do basic map watching', () => {
@ -183,7 +184,6 @@ import {kvChangesAsString, testChangesAsString} from '../../change_detection/uti
previous: ['a[A->null]', 'd[D->null]'],
removals: ['a[A->null]', 'd[D->null]']
}));
});
it('should work regardless key order', () => {
@ -221,7 +221,6 @@ import {kvChangesAsString, testChangesAsString} from '../../change_detection/uti
removals: ['b[b->null]']
}));
});
});
describe('diff', () => {

View File

@ -10,7 +10,11 @@ import {getSymbolIterator} from '@angular/core/src/util/symbol';
export class TestIterable {
list: number[];
constructor() { this.list = []; }
constructor() {
this.list = [];
}
[getSymbolIterator()]() { return (this.list as any)[getSymbolIterator()](); }
[getSymbolIterator()]() {
return (this.list as any)[getSymbolIterator()]();
}
}

View File

@ -47,9 +47,14 @@ function icrAsString<V>(icr: IterableChangeRecord<V>): string {
stringify(icr.previousIndex) + '->' + stringify(icr.currentIndex) + ']';
}
export function iterableChangesAsString(
{collection = [] as any, previous = [] as any, additions = [] as any, moves = [] as any,
removals = [] as any, identityChanges = [] as any}): string {
export function iterableChangesAsString({
collection = [] as any,
previous = [] as any,
additions = [] as any,
moves = [] as any,
removals = [] as any,
identityChanges = [] as any
}): string {
return 'collection: ' + collection.join(', ') + '\n' +
'previous: ' + previous.join(', ') + '\n' +
'additions: ' + additions.join(', ') + '\n' +

View File

@ -7,7 +7,7 @@
*/
import {Component, Injectable, Input} from '@angular/core';
import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, async, withModule} from '@angular/core/testing';
import {async, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, withModule} from '@angular/core/testing';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -15,7 +15,9 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
@Injectable()
class SimpleComp {
simpleBinding: string;
constructor() { this.simpleBinding = 'Simple'; }
constructor() {
this.simpleBinding = 'Simple';
}
}
@Component({
@ -31,7 +33,9 @@ class MyIfComp {
class AutoDetectComp {
text: string = '1';
click() { this.text += '1'; }
click() {
this.text += '1';
}
}
@Component({selector: 'async-comp', template: `<span (click)='click()'>{{text}}</span>`})
@ -39,7 +43,9 @@ class AsyncComp {
text: string = '1';
click() {
Promise.resolve(null).then((_) => { this.text += '1'; });
Promise.resolve(null).then((_) => {
this.text += '1';
});
}
}
@ -49,7 +55,9 @@ class AsyncChildComp {
@Input()
set text(value: string) {
Promise.resolve(null).then((_) => { this.localText = value; });
Promise.resolve(null).then((_) => {
this.localText = value;
});
}
}
@ -60,7 +68,9 @@ class AsyncChildComp {
class AsyncChangeComp {
text: string = '1';
click() { this.text += '1'; }
click() {
this.text += '1';
}
}
@Component({selector: 'async-timeout-comp', template: `<span (click)='click()'>{{text}}</span>`})
@ -68,7 +78,9 @@ class AsyncTimeoutComp {
text: string = '1';
click() {
setTimeout(() => { this.text += '1'; }, 10);
setTimeout(() => {
this.text += '1';
}, 10);
}
}
@ -78,7 +90,11 @@ class NestedAsyncTimeoutComp {
text: string = '1';
click() {
setTimeout(() => { setTimeout(() => { this.text += '1'; }, 10); }, 10);
setTimeout(() => {
setTimeout(() => {
this.text += '1';
}, 10);
}, 10);
}
}
@ -94,7 +110,6 @@ class NestedAsyncTimeoutComp {
}));
it('should auto detect changes if autoDetectChanges is called', () => {
const componentFixture = TestBed.createComponent(AutoDetectComp);
expect(componentFixture.ngZone).not.toBeNull();
componentFixture.autoDetectChanges();
@ -109,7 +124,6 @@ class NestedAsyncTimeoutComp {
it('should auto detect changes if ComponentFixtureAutoDetect is provided as true',
withModule({providers: [{provide: ComponentFixtureAutoDetect, useValue: true}]}, () => {
const componentFixture = TestBed.createComponent(AutoDetectComp);
expect(componentFixture.nativeElement).toHaveText('1');
@ -181,7 +195,6 @@ class NestedAsyncTimeoutComp {
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
async(() => {
const componentFixture = TestBed.createComponent(AsyncTimeoutComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -203,7 +216,6 @@ class NestedAsyncTimeoutComp {
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)',
async(() => {
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
componentFixture.autoDetectChanges();
@ -225,7 +237,6 @@ class NestedAsyncTimeoutComp {
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
async(() => {
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -245,7 +256,6 @@ class NestedAsyncTimeoutComp {
}));
it('should stabilize after async task in change detection (autoDetectChanges)', async(() => {
const componentFixture = TestBed.createComponent(AsyncChangeComp);
componentFixture.autoDetectChanges();
@ -255,13 +265,13 @@ class NestedAsyncTimeoutComp {
const element = componentFixture.debugElement.children[0];
dispatchEvent(element.nativeElement, 'click');
componentFixture.whenStable().then(
(_) => { expect(componentFixture.nativeElement).toHaveText('11'); });
componentFixture.whenStable().then((_) => {
expect(componentFixture.nativeElement).toHaveText('11');
});
});
}));
it('should stabilize after async task in change detection(no autoDetectChanges)', async(() => {
const componentFixture = TestBed.createComponent(AsyncChangeComp);
componentFixture.detectChanges();
componentFixture.whenStable().then((_) => {
@ -290,7 +300,6 @@ class NestedAsyncTimeoutComp {
});
it('calling autoDetectChanges raises an error', () => {
const componentFixture = TestBed.createComponent(SimpleComp);
expect(() => {
componentFixture.autoDetectChanges();
@ -298,7 +307,6 @@ class NestedAsyncTimeoutComp {
});
it('should instantiate a component with valid DOM', async(() => {
const componentFixture = TestBed.createComponent(SimpleComp);
expect(componentFixture.ngZone).toBeNull();
@ -307,7 +315,6 @@ class NestedAsyncTimeoutComp {
}));
it('should allow changing members of the component', async(() => {
const componentFixture = TestBed.createComponent(MyIfComp);
componentFixture.detectChanges();
@ -316,9 +323,7 @@ class NestedAsyncTimeoutComp {
componentFixture.componentInstance.showMore = true;
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
}));
});
});
}

View File

@ -10,7 +10,7 @@
import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common';
import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {NgZone} from '@angular/core/src/zone';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {createMouseEvent, hasClass} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -20,18 +20,26 @@ import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
class Logger {
logs: string[];
constructor() { this.logs = []; }
constructor() {
this.logs = [];
}
add(thing: string) { this.logs.push(thing); }
add(thing: string) {
this.logs.push(thing);
}
}
@Directive({selector: '[message]', inputs: ['message']})
class MessageDir {
logger: Logger;
constructor(logger: Logger) { this.logger = logger; }
constructor(logger: Logger) {
this.logger = logger;
}
set message(newMessage: string) { this.logger.add(newMessage); }
set message(newMessage: string) {
this.logger.add(newMessage);
}
}
@Directive({selector: '[with-title]', inputs: ['title']})
@ -49,7 +57,9 @@ class WithTitleDir {
class ChildComp {
childBinding: string;
constructor() { this.childBinding = 'Original'; }
constructor() {
this.childBinding = 'Original';
}
}
@Component({
@ -63,14 +73,18 @@ class ChildComp {
})
class ParentComp {
parentBinding: string;
constructor() { this.parentBinding = 'OriginalParent'; }
constructor() {
this.parentBinding = 'OriginalParent';
}
}
@Directive({selector: 'custom-emitter', outputs: ['myevent']})
class CustomEmitter {
myevent: EventEmitter<any>;
constructor() { this.myevent = new EventEmitter(); }
constructor() {
this.myevent = new EventEmitter();
}
}
@Component({
@ -87,9 +101,13 @@ class EventsComp {
this.customed = false;
}
handleClick() { this.clicked = true; }
handleClick() {
this.clicked = true;
}
handleCustom() { this.customed = true; }
handleCustom() {
this.customed = true;
}
}
@Component({
@ -111,7 +129,9 @@ class ConditionalContentComp {
})
class ConditionalParentComp {
parentBinding: string;
constructor() { this.parentBinding = 'OriginalParent'; }
constructor() {
this.parentBinding = 'OriginalParent';
}
}
@Component({
@ -124,7 +144,9 @@ class ConditionalParentComp {
})
class UsingFor {
stuff: string[];
constructor() { this.stuff = ['one', 'two', 'three']; }
constructor() {
this.stuff = ['one', 'two', 'three'];
}
}
@Directive({selector: '[mydir]', exportAs: 'mydir'})
@ -154,12 +176,12 @@ class LocalsComp {
})
class BankAccount {
// TODO(issue/24571): remove '!'.
@Input() bank !: string;
@Input() bank!: string;
// TODO(issue/24571): remove '!'.
@Input('account') id !: string;
@Input('account') id!: string;
// TODO(issue/24571): remove '!'.
normalizedBankName !: string;
normalizedBankName!: string;
}
@Component({
@ -168,7 +190,7 @@ class BankAccount {
`
})
class SimpleContentComp {
@ViewChild('content') content !: ElementRef;
@ViewChild('content') content!: ElementRef;
}
@Component({
@ -199,8 +221,7 @@ class TestCmptWithRenderer {
@Component({selector: 'host-class-binding', template: ''})
class HostClassBindingCmp {
@HostBinding('class')
hostClasses = 'class-one class-two';
@HostBinding('class') hostClasses = 'class-one class-two';
}
@Component({selector: 'test-cmpt-vcref', template: `<div></div>`})
@ -525,11 +546,15 @@ class TestCmptWithPropInterpolation {
class ViewManipulatingDirective {
constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<any>) {}
insert() { this._vcRef.createEmbeddedView(this._tplRef); }
insert() {
this._vcRef.createEmbeddedView(this._tplRef);
}
removeFromTheDom() {
const viewRef = this._vcRef.get(0) as EmbeddedViewRef<any>;
viewRef.rootNodes.forEach(rootNode => { getDOM().remove(rootNode); });
viewRef.rootNodes.forEach(rootNode => {
getDOM().remove(rootNode);
});
}
}
@ -596,7 +621,6 @@ class TestCmptWithPropInterpolation {
describe('DebugElement.query with dynamically created descendant elements', () => {
let fixture: ComponentFixture<{}>;
beforeEach(() => {
@Directive({
selector: '[dir]',
})
@ -627,11 +651,11 @@ class TestCmptWithPropInterpolation {
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
});
it('should find the dynamic elements from fixture root',
() => { expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy(); });
it('should find the dynamic elements from fixture root', () => {
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
});
it('can use a dynamic element as root for another query', () => {
const inner = fixture.debugElement.query(By.css('.inner'));
@ -660,15 +684,17 @@ class TestCmptWithPropInterpolation {
el = fixture.debugElement;
});
it('when searching for elements by name',
() => { expect(() => el.query(e => e.name === 'any search text')).not.toThrow(); });
it('when searching for elements by their attributes', () => {
expect(() => el.query(e => e.attributes !['name'] === 'any attribute')).not.toThrow();
it('when searching for elements by name', () => {
expect(() => el.query(e => e.name === 'any search text')).not.toThrow();
});
it('when searching for elements by their classes',
() => { expect(() => el.query(e => e.classes['any class'] === true)).not.toThrow(); });
it('when searching for elements by their attributes', () => {
expect(() => el.query(e => e.attributes!['name'] === 'any attribute')).not.toThrow();
});
it('when searching for elements by their classes', () => {
expect(() => el.query(e => e.classes['any class'] === true)).not.toThrow();
});
it('when searching for elements by their styles', () => {
expect(() => el.query(e => e.styles['any style'] === 'any value')).not.toThrow();
@ -678,23 +704,29 @@ class TestCmptWithPropInterpolation {
expect(() => el.query(e => e.properties['any prop'] === 'any value')).not.toThrow();
});
it('when searching by componentInstance',
() => { expect(() => el.query(e => e.componentInstance === null)).not.toThrow(); });
it('when searching by componentInstance', () => {
expect(() => el.query(e => e.componentInstance === null)).not.toThrow();
});
it('when searching by context',
() => { expect(() => el.query(e => e.context === null)).not.toThrow(); });
it('when searching by context', () => {
expect(() => el.query(e => e.context === null)).not.toThrow();
});
it('when searching by listeners',
() => { expect(() => el.query(e => e.listeners.length === 0)).not.toThrow(); });
it('when searching by listeners', () => {
expect(() => el.query(e => e.listeners.length === 0)).not.toThrow();
});
it('when searching by references',
() => { expect(() => el.query(e => e.references === null)).not.toThrow(); });
it('when searching by references', () => {
expect(() => el.query(e => e.references === null)).not.toThrow();
});
it('when searching by providerTokens',
() => { expect(() => el.query(e => e.providerTokens.length === 0)).not.toThrow(); });
it('when searching by providerTokens', () => {
expect(() => el.query(e => e.providerTokens.length === 0)).not.toThrow();
});
it('when searching by injector',
() => { expect(() => el.query(e => e.injector === null)).not.toThrow(); });
it('when searching by injector', () => {
expect(() => el.query(e => e.injector === null)).not.toThrow();
});
onlyInIvy('VE does not match elements created outside Angular context')
.it('when using the out-of-context element as the DebugElement query root', () => {
@ -746,7 +778,7 @@ class TestCmptWithPropInterpolation {
fixture = TestBed.createComponent(LocalsComp);
fixture.detectChanges();
expect(fixture.debugElement.children[0].references !['alice']).toBeAnInstanceOf(MyDir);
expect(fixture.debugElement.children[0].references!['alice']).toBeAnInstanceOf(MyDir);
});
it('should allow injecting from the element injector', () => {
@ -941,7 +973,6 @@ class TestCmptWithPropInterpolation {
});
describe('componentInstance on DebugNode', () => {
it('should return component associated with a node if a node is a component host', () => {
TestBed.overrideTemplate(TestCmpt, `<parent-comp></parent-comp>`);
fixture = TestBed.createComponent(TestCmpt);
@ -1199,7 +1230,9 @@ class TestCmptWithPropInterpolation {
})
class App {
visible = true;
cancel() { calls++; }
cancel() {
calls++;
}
}
TestBed.configureTestingModule({declarations: [App, CancelButton]});
@ -1218,7 +1251,6 @@ class TestCmptWithPropInterpolation {
expect(calls).toBe(1, 'Expected calls to stay 1 after destroying the node.');
});
});
it('should not error when accessing node name', () => {
@ -1234,7 +1266,7 @@ class TestCmptWithPropInterpolation {
// Node.ELEMENT_NODE
while (node) {
superParentName = node.name;
node = node.parent !;
node = node.parent!;
}
expect(superParentName).not.toEqual('');
});
@ -1257,13 +1289,16 @@ class TestCmptWithPropInterpolation {
it('does not call event listeners added outside angular context', () => {
let listenerCalled = false;
const eventToTrigger = createMouseEvent('mouseenter');
function listener() { listenerCalled = true; }
function listener() {
listenerCalled = true;
}
@Component({template: ''})
class MyComp {
constructor(private readonly zone: NgZone, private readonly element: ElementRef) {}
ngOnInit() {
this.zone.runOutsideAngular(
() => { this.element.nativeElement.addEventListener('mouseenter', listener); });
this.zone.runOutsideAngular(() => {
this.element.nativeElement.addEventListener('mouseenter', listener);
});
}
}
const fixture =

View File

@ -10,6 +10,8 @@ import {isDevMode} from '@angular/core';
{
describe('dev mode', () => {
it('is enabled in our tests by default', () => { expect(isDevMode()).toBe(true); });
it('is enabled in our tests by default', () => {
expect(isDevMode()).toBe(true);
});
});
}

View File

@ -22,7 +22,8 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
.toThrowError('NullInjectorError: No provider for someToken!');
});
it('should return the default value',
() => { expect(Injector.NULL.get('someToken', 'notFound')).toEqual('notFound'); });
it('should return the default value', () => {
expect(Injector.NULL.get('someToken', 'notFound')).toEqual('notFound');
});
});
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {INJECTOR, InjectFlags, InjectionToken, Injector, Optional, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '@angular/core';
import {R3Injector, createInjector} from '@angular/core/src/di/r3_injector';
import {InjectFlags, InjectionToken, INJECTOR, Injector, Optional, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '@angular/core';
import {createInjector, R3Injector} from '@angular/core/src/di/r3_injector';
import {expect} from '@angular/platform-browser/testing/src/matchers';
describe('InjectorDef-based createInjector()', () => {
@ -64,7 +64,7 @@ describe('InjectorDef-based createInjector()', () => {
providedIn: null,
// ChildService is derived from ServiceWithDep, so the factory function here must do the right
// thing and create an instance of the requested type if one is given.
factory: (t?: typeof ServiceWithDep) => new (t || ServiceWithDep)(ɵɵinject(Service)),
factory: (t?: typeof ServiceWithDep) => new(t || ServiceWithDep)(ɵɵinject(Service)),
});
}
@ -114,7 +114,9 @@ describe('InjectorDef-based createInjector()', () => {
factory: () => new DeepService(),
});
ngOnDestroy(): void { deepServiceDestroyed = true; }
ngOnDestroy(): void {
deepServiceDestroyed = true;
}
}
let eagerServiceCreated: boolean = false;
@ -125,20 +127,31 @@ describe('InjectorDef-based createInjector()', () => {
factory: () => new EagerService(),
});
constructor() { eagerServiceCreated = true; }
constructor() {
eagerServiceCreated = true;
}
}
let deepModuleCreated: boolean = false;
class DeepModule {
constructor(eagerService: EagerService) { deepModuleCreated = true; }
constructor(eagerService: EagerService) {
deepModuleCreated = true;
}
static ɵinj = ɵɵdefineInjector({
factory: () => new DeepModule(ɵɵinject(EagerService)),
imports: undefined,
providers: [
EagerService,
{provide: DeepService, useFactory: () => { throw new Error('Not overridden!'); }},
],
providers:
[
EagerService,
{
provide: DeepService,
useFactory:
() => {
throw new Error('Not overridden!');
}
},
],
});
static safe() {
@ -171,22 +184,23 @@ describe('InjectorDef-based createInjector()', () => {
static ɵinj = ɵɵdefineInjector({
factory: () => new Module(),
imports: [IntermediateModule],
providers: [
ChildService,
ServiceWithDep,
ServiceWithOptionalDep,
ServiceWithMultiDep,
{provide: LOCALE, multi: true, useValue: 'en'},
{provide: LOCALE, multi: true, useValue: 'es'},
{provide: PRIMITIVE_VALUE, useValue: 'foo'},
{provide: UNDEFINED_VALUE, useValue: undefined},
Service,
{provide: SERVICE_TOKEN, useExisting: Service},
CircularA,
CircularB,
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
InjectorWithDep,
],
providers:
[
ChildService,
ServiceWithDep,
ServiceWithOptionalDep,
ServiceWithMultiDep,
{provide: LOCALE, multi: true, useValue: 'en'},
{provide: LOCALE, multi: true, useValue: 'es'},
{provide: PRIMITIVE_VALUE, useValue: 'foo'},
{provide: UNDEFINED_VALUE, useValue: undefined},
Service,
{provide: SERVICE_TOKEN, useExisting: Service},
CircularA,
CircularB,
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
InjectorWithDep,
],
});
}
@ -224,7 +238,9 @@ describe('InjectorDef-based createInjector()', () => {
factory: () => new ScopedService(),
});
ngOnDestroy(): void { scopedServiceDestroyed = true; }
ngOnDestroy(): void {
scopedServiceDestroyed = true;
}
}
class WrongScopeService {
@ -252,10 +268,11 @@ describe('InjectorDef-based createInjector()', () => {
class WithProvidersTest {
static ɵinj = ɵɵdefineInjector({
factory: () => new WithProvidersTest(),
imports: [
{ngModule: MultiProviderA, providers: [{provide: LOCALE, multi: true, useValue: 'C'}]},
MultiProviderB
],
imports:
[
{ngModule: MultiProviderA, providers: [{provide: LOCALE, multi: true, useValue: 'C'}]},
MultiProviderB
],
providers: [],
});
}
@ -276,7 +293,9 @@ describe('InjectorDef-based createInjector()', () => {
imports: undefined,
providers: [],
});
constructor() { moduleRegistrations.push('ChildModule'); }
constructor() {
moduleRegistrations.push('ChildModule');
}
}
class RootModule {
@ -285,7 +304,9 @@ describe('InjectorDef-based createInjector()', () => {
imports: [ChildModule],
providers: [],
});
constructor() { moduleRegistrations.push('RootModule'); }
constructor() {
moduleRegistrations.push('RootModule');
}
}
createInjector(RootModule);
expect(moduleRegistrations).toEqual(['ChildModule', 'RootModule']);
@ -297,8 +318,9 @@ describe('InjectorDef-based createInjector()', () => {
expect(injector.get(Service)).toBe(instance);
});
it('returns the default value if a provider isn\'t present',
() => { expect(injector.get(ServiceTwo, null)).toBeNull(); });
it('returns the default value if a provider isn\'t present', () => {
expect(injector.get(ServiceTwo, null)).toBeNull();
});
it('should throw when no provider defined', () => {
expect(() => injector.get(ServiceTwo))
@ -373,14 +395,17 @@ describe('InjectorDef-based createInjector()', () => {
expect(instance.dep).toBe(injector.get(Service));
});
it('allows injecting itself via INJECTOR',
() => { expect(injector.get(INJECTOR)).toBe(injector); });
it('allows injecting itself via INJECTOR', () => {
expect(injector.get(INJECTOR)).toBe(injector);
});
it('allows injecting itself via Injector',
() => { expect(injector.get(Injector)).toBe(injector); });
it('allows injecting itself via Injector', () => {
expect(injector.get(Injector)).toBe(injector);
});
it('allows injecting a deeply imported service',
() => { expect(injector.get(DeepService) instanceof DeepService).toBeTruthy(); });
it('allows injecting a deeply imported service', () => {
expect(injector.get(DeepService) instanceof DeepService).toBeTruthy();
});
it('allows injecting a scoped service', () => {
const instance = injector.get(ScopedService);
@ -393,8 +418,9 @@ describe('InjectorDef-based createInjector()', () => {
expect(instance instanceof ChildService).toBe(true);
});
it('does not create instances of a service not in scope',
() => { expect(injector.get(WrongScopeService, null)).toBeNull(); });
it('does not create instances of a service not in scope', () => {
expect(injector.get(WrongScopeService, null)).toBeNull();
});
it('eagerly instantiates the injectordef types', () => {
expect(deepModuleCreated).toBe(true, 'DeepModule not instantiated');
@ -432,11 +458,13 @@ describe('InjectorDef-based createInjector()', () => {
});
describe('error handling', () => {
it('throws an error when a token is not found',
() => { expect(() => injector.get(ServiceTwo)).toThrow(); });
it('throws an error when a token is not found', () => {
expect(() => injector.get(ServiceTwo)).toThrow();
});
it('throws an error on circular deps',
() => { expect(() => injector.get(CircularA)).toThrow(); });
it('throws an error on circular deps', () => {
expect(() => injector.get(CircularA)).toThrow();
});
it('should throw when it can\'t resolve all arguments', () => {
class MissingArgumentType {

View File

@ -6,17 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Inject, Injectable, InjectionToken, Injector, Optional, Provider, ReflectiveInjector, ReflectiveKey, Self, forwardRef} from '@angular/core';
import {forwardRef, Inject, Injectable, InjectionToken, Injector, Optional, Provider, ReflectiveInjector, ReflectiveKey, Self} from '@angular/core';
import {ReflectiveInjector_} from '@angular/core/src/di/reflective_injector';
import {ResolvedReflectiveProvider_} from '@angular/core/src/di/reflective_provider';
import {getOriginalError} from '@angular/core/src/errors';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {stringify} from '../../src/util/stringify';
class Engine {}
class BrokenEngine {
constructor() { throw new Error('Broken Engine'); }
constructor() {
throw new Error('Broken Engine');
}
}
class DashboardSoftware {}
@ -66,464 +69,456 @@ class NoAnnotations {
constructor(secretDependency: any) {}
}
function factoryFn(a: any){}
function factoryFn(a: any) {}
(function() {
const dynamicProviders = [
{provide: 'provider0', useValue: 1}, {provide: 'provider1', useValue: 1},
{provide: 'provider2', useValue: 1}, {provide: 'provider3', useValue: 1},
{provide: 'provider4', useValue: 1}, {provide: 'provider5', useValue: 1},
{provide: 'provider6', useValue: 1}, {provide: 'provider7', useValue: 1},
{provide: 'provider8', useValue: 1}, {provide: 'provider9', useValue: 1},
{provide: 'provider10', useValue: 1}
];
const dynamicProviders = [
{provide: 'provider0', useValue: 1}, {provide: 'provider1', useValue: 1},
{provide: 'provider2', useValue: 1}, {provide: 'provider3', useValue: 1},
{provide: 'provider4', useValue: 1}, {provide: 'provider5', useValue: 1},
{provide: 'provider6', useValue: 1}, {provide: 'provider7', useValue: 1},
{provide: 'provider8', useValue: 1}, {provide: 'provider9', useValue: 1},
{provide: 'provider10', useValue: 1}
];
function createInjector(
providers: Provider[], parent?: ReflectiveInjector | null): ReflectiveInjector_ {
const resolvedProviders = ReflectiveInjector.resolve(providers.concat(dynamicProviders));
if (parent != null) {
return <ReflectiveInjector_>parent.createChildFromResolved(resolvedProviders);
} else {
return <ReflectiveInjector_>ReflectiveInjector.fromResolvedProviders(resolvedProviders);
}
function createInjector(
providers: Provider[], parent?: ReflectiveInjector|null): ReflectiveInjector_ {
const resolvedProviders = ReflectiveInjector.resolve(providers.concat(dynamicProviders));
if (parent != null) {
return <ReflectiveInjector_>parent.createChildFromResolved(resolvedProviders);
} else {
return <ReflectiveInjector_>ReflectiveInjector.fromResolvedProviders(resolvedProviders);
}
}
describe(`injector`, () => {
it('should instantiate a class without dependencies', () => {
const injector = createInjector([Engine]);
const engine = injector.get(Engine);
expect(engine).toBeAnInstanceOf(Engine);
});
it('should resolve dependencies based on type information', () => {
const injector = createInjector([Engine, Car]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should resolve dependencies based on @Inject annotation', () => {
const injector = createInjector([TurboEngine, Engine, CarWithInject]);
const car = injector.get(CarWithInject);
expect(car).toBeAnInstanceOf(CarWithInject);
expect(car.engine).toBeAnInstanceOf(TurboEngine);
});
it('should throw when no type and not @Inject (class case)', () => {
expect(() => createInjector([NoAnnotations]))
.toThrowError(
'Cannot resolve all parameters for \'NoAnnotations\'(?). ' +
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
'and that \'NoAnnotations\' is decorated with Injectable.');
});
it('should throw when no type and not @Inject (factory case)', () => {
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
.toThrowError(
'Cannot resolve all parameters for \'factoryFn\'(?). ' +
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
'and that \'factoryFn\' is decorated with Injectable.');
});
it('should cache instances', () => {
const injector = createInjector([Engine]);
const e1 = injector.get(Engine);
const e2 = injector.get(Engine);
expect(e1).toBe(e2);
});
it('should provide to a value', () => {
const injector = createInjector([{provide: Engine, useValue: 'fake engine'}]);
const engine = injector.get(Engine);
expect(engine).toEqual('fake engine');
});
it('should inject dependencies instance of InjectionToken', () => {
const TOKEN = new InjectionToken<string>('token');
const injector = createInjector([
{provide: TOKEN, useValue: 'by token'},
{provide: Engine, useFactory: (v: string) => v, deps: [[TOKEN]]},
]);
const engine = injector.get(Engine);
expect(engine).toEqual('by token');
});
it('should provide to a factory', () => {
function sportsCarFactory(e: any) { return new SportsCar(e); }
const injector =
createInjector([Engine, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should supporting provider to null', () => {
const injector = createInjector([{provide: Engine, useValue: null}]);
const engine = injector.get(Engine);
expect(engine).toBeNull();
});
it('should provide to an alias', () => {
const injector = createInjector([
Engine, {provide: SportsCar, useClass: SportsCar}, {provide: Car, useExisting: SportsCar}
]);
const car = injector.get(Car);
const sportsCar = injector.get(SportsCar);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car).toBe(sportsCar);
});
it('should support multiProviders', () => {
const injector = createInjector([
Engine, {provide: Car, useClass: SportsCar, multi: true},
{provide: Car, useClass: CarWithOptionalEngine, multi: true}
]);
const cars = injector.get(Car);
expect(cars.length).toEqual(2);
expect(cars[0]).toBeAnInstanceOf(SportsCar);
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
});
it('should support multiProviders that are created using useExisting', () => {
const injector =
createInjector([Engine, SportsCar, {provide: Car, useExisting: SportsCar, multi: true}]);
const cars = injector.get(Car);
expect(cars.length).toEqual(1);
expect(cars[0]).toBe(injector.get(SportsCar));
});
it('should throw when the aliased provider does not exist', () => {
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
const e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
expect(() => injector.get('car')).toThrowError(e);
});
it('should handle forwardRef in useExisting', () => {
const injector = createInjector([
{provide: 'originalEngine', useClass: forwardRef(() => Engine)},
{provide: 'aliasedEngine', useExisting: <any>forwardRef(() => 'originalEngine')}
]);
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
});
it('should support overriding factory dependencies', () => {
const injector = createInjector(
[Engine, {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should support optional dependencies', () => {
const injector = createInjector([CarWithOptionalEngine]);
const car = injector.get(CarWithOptionalEngine);
expect(car.engine).toEqual(null);
});
it('should flatten passed-in providers', () => {
const injector = createInjector([[[Engine, Car]]]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
});
it('should use the last provider when there are multiple providers for same token', () => {
const injector = createInjector(
[{provide: Engine, useClass: Engine}, {provide: Engine, useClass: TurboEngine}]);
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
});
it('should use non-type tokens', () => {
const injector = createInjector([{provide: 'token', useValue: 'value'}]);
expect(injector.get('token')).toEqual('value');
});
it('should throw when given invalid providers', () => {
expect(() => createInjector(<any>['blah']))
.toThrowError(
'Invalid provider - only instances of Provider and Type are allowed, got: blah');
});
it('should provide itself', () => {
const parent = createInjector([]);
const child = parent.resolveAndCreateChild([]);
expect(child.get(Injector)).toBe(child);
});
it('should throw when no provider defined', () => {
const injector = createInjector([]);
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
});
it('should show the full path when no provider', () => {
const injector = createInjector([CarWithDashboard, Engine, Dashboard]);
expect(() => injector.get(CarWithDashboard))
.toThrowError(
`No provider for DashboardSoftware! (${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware)`);
});
it('should throw when trying to instantiate a cyclic dependency', () => {
const injector = createInjector([Car, {provide: Engine, useClass: CyclicEngine}]);
expect(() => injector.get(Car))
.toThrowError(
`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)})`);
});
it('should show the full path when error happens in a constructor', () => {
const providers =
ReflectiveInjector.resolve([Car, {provide: Engine, useClass: BrokenEngine}]);
const injector = new ReflectiveInjector_(providers);
try {
injector.get(Car);
throw 'Must throw';
} catch (e) {
expect(e.message).toContain(
`Error during instantiation of Engine! (${stringify(Car)} -> Engine)`);
expect(getOriginalError(e) instanceof Error).toBeTruthy();
expect(e.keys[0].token).toEqual(Engine);
}
});
it('should instantiate an object after a failed attempt', () => {
let isBroken = true;
const injector = createInjector([
Car, {provide: Engine, useFactory: (() => isBroken ? new BrokenEngine() : new Engine())}
]);
expect(() => injector.get(Car))
.toThrowError('Broken Engine: Error during instantiation of Engine! (Car -> Engine).');
isBroken = false;
expect(injector.get(Car)).toBeAnInstanceOf(Car);
});
it('should support null values', () => {
const injector = createInjector([{provide: 'null', useValue: null}]);
expect(injector.get('null')).toBe(null);
});
describe(`injector`, () => {
it('should instantiate a class without dependencies', () => {
const injector = createInjector([Engine]);
const engine = injector.get(Engine);
expect(engine).toBeAnInstanceOf(Engine);
});
it('should resolve dependencies based on type information', () => {
const injector = createInjector([Engine, Car]);
const car = injector.get(Car);
describe('child', () => {
it('should load instances from parent injector', () => {
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should resolve dependencies based on @Inject annotation', () => {
const injector = createInjector([TurboEngine, Engine, CarWithInject]);
const car = injector.get(CarWithInject);
expect(car).toBeAnInstanceOf(CarWithInject);
expect(car.engine).toBeAnInstanceOf(TurboEngine);
});
it('should throw when no type and not @Inject (class case)', () => {
expect(() => createInjector([NoAnnotations]))
.toThrowError(
'Cannot resolve all parameters for \'NoAnnotations\'(?). ' +
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
'and that \'NoAnnotations\' is decorated with Injectable.');
});
it('should throw when no type and not @Inject (factory case)', () => {
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
.toThrowError(
'Cannot resolve all parameters for \'factoryFn\'(?). ' +
'Make sure that all the parameters are decorated with Inject or have valid type annotations ' +
'and that \'factoryFn\' is decorated with Injectable.');
});
it('should cache instances', () => {
const injector = createInjector([Engine]);
const e1 = injector.get(Engine);
const e2 = injector.get(Engine);
expect(e1).toBe(e2);
});
it('should provide to a value', () => {
const injector = createInjector([{provide: Engine, useValue: 'fake engine'}]);
const engine = injector.get(Engine);
expect(engine).toEqual('fake engine');
});
it('should inject dependencies instance of InjectionToken', () => {
const TOKEN = new InjectionToken<string>('token');
const injector = createInjector([
{provide: TOKEN, useValue: 'by token'},
{provide: Engine, useFactory: (v: string) => v, deps: [[TOKEN]]},
]);
const engine = injector.get(Engine);
expect(engine).toEqual('by token');
});
it('should provide to a factory', () => {
function sportsCarFactory(e: any) {
return new SportsCar(e);
}
const injector =
createInjector([Engine, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should supporting provider to null', () => {
const injector = createInjector([{provide: Engine, useValue: null}]);
const engine = injector.get(Engine);
expect(engine).toBeNull();
});
it('should provide to an alias', () => {
const injector = createInjector([
Engine, {provide: SportsCar, useClass: SportsCar}, {provide: Car, useExisting: SportsCar}
]);
const car = injector.get(Car);
const sportsCar = injector.get(SportsCar);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car).toBe(sportsCar);
});
it('should support multiProviders', () => {
const injector = createInjector([
Engine, {provide: Car, useClass: SportsCar, multi: true},
{provide: Car, useClass: CarWithOptionalEngine, multi: true}
]);
const cars = injector.get(Car);
expect(cars.length).toEqual(2);
expect(cars[0]).toBeAnInstanceOf(SportsCar);
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
});
it('should support multiProviders that are created using useExisting', () => {
const injector =
createInjector([Engine, SportsCar, {provide: Car, useExisting: SportsCar, multi: true}]);
const cars = injector.get(Car);
expect(cars.length).toEqual(1);
expect(cars[0]).toBe(injector.get(SportsCar));
});
it('should throw when the aliased provider does not exist', () => {
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
const e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
expect(() => injector.get('car')).toThrowError(e);
});
it('should handle forwardRef in useExisting', () => {
const injector = createInjector([
{provide: 'originalEngine', useClass: forwardRef(() => Engine)},
{provide: 'aliasedEngine', useExisting: <any>forwardRef(() => 'originalEngine')}
]);
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
});
it('should support overriding factory dependencies', () => {
const injector = createInjector(
[Engine, {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should support optional dependencies', () => {
const injector = createInjector([CarWithOptionalEngine]);
const car = injector.get(CarWithOptionalEngine);
expect(car.engine).toEqual(null);
});
it('should flatten passed-in providers', () => {
const injector = createInjector([[[Engine, Car]]]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
});
it('should use the last provider when there are multiple providers for same token', () => {
const injector = createInjector(
[{provide: Engine, useClass: Engine}, {provide: Engine, useClass: TurboEngine}]);
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
});
it('should use non-type tokens', () => {
const injector = createInjector([{provide: 'token', useValue: 'value'}]);
expect(injector.get('token')).toEqual('value');
});
it('should throw when given invalid providers', () => {
expect(() => createInjector(<any>['blah']))
.toThrowError(
'Invalid provider - only instances of Provider and Type are allowed, got: blah');
});
it('should provide itself', () => {
const parent = createInjector([]);
const child = parent.resolveAndCreateChild([]);
expect(child.get(Injector)).toBe(child);
});
it('should throw when no provider defined', () => {
const injector = createInjector([]);
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
});
it('should show the full path when no provider', () => {
const injector = createInjector([CarWithDashboard, Engine, Dashboard]);
expect(() => injector.get(CarWithDashboard))
.toThrowError(`No provider for DashboardSoftware! (${stringify(CarWithDashboard)} -> ${
stringify(Dashboard)} -> DashboardSoftware)`);
});
it('should throw when trying to instantiate a cyclic dependency', () => {
const injector = createInjector([Car, {provide: Engine, useClass: CyclicEngine}]);
expect(() => injector.get(Car))
.toThrowError(`Cannot instantiate cyclic dependency! (${stringify(Car)} -> ${
stringify(Engine)} -> ${stringify(Car)})`);
});
it('should show the full path when error happens in a constructor', () => {
const providers = ReflectiveInjector.resolve([Car, {provide: Engine, useClass: BrokenEngine}]);
const injector = new ReflectiveInjector_(providers);
try {
injector.get(Car);
throw 'Must throw';
} catch (e) {
expect(e.message).toContain(
`Error during instantiation of Engine! (${stringify(Car)} -> Engine)`);
expect(getOriginalError(e) instanceof Error).toBeTruthy();
expect(e.keys[0].token).toEqual(Engine);
}
});
it('should instantiate an object after a failed attempt', () => {
let isBroken = true;
const injector = createInjector(
[Car, {provide: Engine, useFactory: (() => isBroken ? new BrokenEngine() : new Engine())}]);
expect(() => injector.get(Car))
.toThrowError('Broken Engine: Error during instantiation of Engine! (Car -> Engine).');
isBroken = false;
expect(injector.get(Car)).toBeAnInstanceOf(Car);
});
it('should support null values', () => {
const injector = createInjector([{provide: 'null', useValue: null}]);
expect(injector.get('null')).toBe(null);
});
});
describe('child', () => {
it('should load instances from parent injector', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild([]);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromChild).toBe(engineFromParent);
});
it('should not use the child providers when resolving the dependencies of a parent provider',
() => {
const parent = ReflectiveInjector.resolveAndCreate([Car, Engine]);
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
const carFromChild = child.get(Car);
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
});
it('should create new instance in a child injector', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
it('should give access to parent', () => {
const parent = ReflectiveInjector.resolveAndCreate([]);
const child = parent.resolveAndCreateChild([]);
expect(child.parent).toBe(parent);
});
});
describe('resolveAndInstantiate', () => {
it('should instantiate an object in the context of the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
const car = inj.resolveAndInstantiate(Car);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBe(inj.get(Engine));
});
it('should not store the instantiated object in the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
inj.resolveAndInstantiate(Car);
expect(() => inj.get(Car)).toThrowError();
});
});
describe('instantiate', () => {
it('should instantiate an object in the context of the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
const car = inj.instantiateResolved(ReflectiveInjector.resolve([Car])[0]);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBe(inj.get(Engine));
});
});
describe('depedency resolution', () => {
describe('@Self()', () => {
it('should return a dependency from self', () => {
const inj = ReflectiveInjector.resolveAndCreate([
Engine, {provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
]);
expect(inj.get(Car)).toBeAnInstanceOf(Car);
});
it('should throw when not requested provider on self', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild([]);
const child = parent.resolveAndCreateChild(
[{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}]);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromChild).toBe(engineFromParent);
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
});
it('should not use the child providers when resolving the dependencies of a parent provider',
() => {
const parent = ReflectiveInjector.resolveAndCreate([Car, Engine]);
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
const carFromChild = child.get(Car);
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
});
it('should create new instance in a child injector', () => {
describe('default', () => {
it('should not skip self', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild([{provide: Engine, useClass: TurboEngine}]);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
it('should give access to parent', () => {
const parent = ReflectiveInjector.resolveAndCreate([]);
const child = parent.resolveAndCreateChild([]);
expect(child.parent).toBe(parent);
});
});
describe('resolveAndInstantiate', () => {
it('should instantiate an object in the context of the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
const car = inj.resolveAndInstantiate(Car);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBe(inj.get(Engine));
});
it('should not store the instantiated object in the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
inj.resolveAndInstantiate(Car);
expect(() => inj.get(Car)).toThrowError();
});
});
describe('instantiate', () => {
it('should instantiate an object in the context of the injector', () => {
const inj = ReflectiveInjector.resolveAndCreate([Engine]);
const car = inj.instantiateResolved(ReflectiveInjector.resolve([Car])[0]);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBe(inj.get(Engine));
});
});
describe('depedency resolution', () => {
describe('@Self()', () => {
it('should return a dependency from self', () => {
const inj = ReflectiveInjector.resolveAndCreate([
Engine,
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
]);
expect(inj.get(Car)).toBeAnInstanceOf(Car);
});
it('should throw when not requested provider on self', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild(
[{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}]);
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
});
describe('default', () => {
it('should not skip self', () => {
const parent = ReflectiveInjector.resolveAndCreate([Engine]);
const child = parent.resolveAndCreateChild([
{provide: Engine, useClass: TurboEngine},
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [Engine]}
]);
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
});
});
});
describe('resolve', () => {
it('should resolve and flatten', () => {
const providers = ReflectiveInjector.resolve([Engine, [BrokenEngine]]);
providers.forEach(function(b) {
if (!b) return; // the result is a sparse array
expect(b instanceof ResolvedReflectiveProvider_).toBe(true);
});
});
it('should support multi providers', () => {
const provider = ReflectiveInjector.resolve([
{provide: Engine, useClass: BrokenEngine, multi: true},
{provide: Engine, useClass: TurboEngine, multi: true}
])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(2);
});
it('should support providers as hash', () => {
const provider = ReflectiveInjector.resolve([
{provide: Engine, useClass: BrokenEngine, multi: true},
{provide: Engine, useClass: TurboEngine, multi: true}
])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(2);
});
it('should support multi providers with only one provider', () => {
const provider =
ReflectiveInjector.resolve([{provide: Engine, useClass: BrokenEngine, multi: true}])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(1);
});
it('should throw when mixing multi providers with regular providers', () => {
expect(() => {
ReflectiveInjector.resolve(
[{provide: Engine, useClass: BrokenEngine, multi: true}, Engine]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
expect(() => {
ReflectiveInjector.resolve(
[Engine, {provide: Engine, useClass: BrokenEngine, multi: true}]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
});
it('should resolve forward references', () => {
const providers = ReflectiveInjector.resolve([
forwardRef(() => Engine),
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine)}], {
provide: forwardRef(() => String),
useFactory: () => 'OK',
deps: [forwardRef(() => Engine)]
}
const child = parent.resolveAndCreateChild([
{provide: Engine, useClass: TurboEngine},
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [Engine]}
]);
const engineProvider = providers[0];
const brokenEngineProvider = providers[1];
const stringProvider = providers[2];
expect(engineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(brokenEngineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(stringProvider.resolvedFactories[0].dependencies[0].key)
.toEqual(ReflectiveKey.get(Engine));
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
});
});
});
it('should support overriding factory dependencies with dependency annotations', () => {
const providers = ReflectiveInjector.resolve([{
provide: 'token',
useFactory: (e: any /** TODO #9100 */) => 'result',
deps: [[new Inject('dep')]]
}]);
const provider = providers[0];
expect(provider.resolvedFactories[0].dependencies[0].key.token).toEqual('dep');
});
it('should allow declaring dependencies with flat arrays', () => {
const resolved = ReflectiveInjector.resolve(
[{provide: 'token', useFactory: (e: any) => e, deps: [new Inject('dep')]}]);
const nestedResolved = ReflectiveInjector.resolve(
[{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject('dep')]]}]);
expect(resolved[0].resolvedFactories[0].dependencies[0].key.token)
.toEqual(nestedResolved[0].resolvedFactories[0].dependencies[0].key.token);
describe('resolve', () => {
it('should resolve and flatten', () => {
const providers = ReflectiveInjector.resolve([Engine, [BrokenEngine]]);
providers.forEach(function(b) {
if (!b) return; // the result is a sparse array
expect(b instanceof ResolvedReflectiveProvider_).toBe(true);
});
});
describe('displayName', () => {
it('should work', () => {
expect((<ReflectiveInjector_>ReflectiveInjector.resolveAndCreate([Engine, BrokenEngine]))
.displayName)
.toEqual('ReflectiveInjector(providers: [ "Engine" , "BrokenEngine" ])');
});
it('should support multi providers', () => {
const provider = ReflectiveInjector.resolve([
{provide: Engine, useClass: BrokenEngine, multi: true},
{provide: Engine, useClass: TurboEngine, multi: true}
])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(2);
});
it('should support providers as hash', () => {
const provider = ReflectiveInjector.resolve([
{provide: Engine, useClass: BrokenEngine, multi: true},
{provide: Engine, useClass: TurboEngine, multi: true}
])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(2);
});
it('should support multi providers with only one provider', () => {
const provider =
ReflectiveInjector.resolve([{provide: Engine, useClass: BrokenEngine, multi: true}])[0];
expect(provider.key.token).toBe(Engine);
expect(provider.multiProvider).toEqual(true);
expect(provider.resolvedFactories.length).toEqual(1);
});
it('should throw when mixing multi providers with regular providers', () => {
expect(() => {
ReflectiveInjector.resolve([{provide: Engine, useClass: BrokenEngine, multi: true}, Engine]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
expect(() => {
ReflectiveInjector.resolve([Engine, {provide: Engine, useClass: BrokenEngine, multi: true}]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
});
it('should resolve forward references', () => {
const providers = ReflectiveInjector.resolve([
forwardRef(() => Engine),
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine)}],
{provide: forwardRef(() => String), useFactory: () => 'OK', deps: [forwardRef(() => Engine)]}
]);
const engineProvider = providers[0];
const brokenEngineProvider = providers[1];
const stringProvider = providers[2];
expect(engineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(brokenEngineProvider.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(stringProvider.resolvedFactories[0].dependencies[0].key)
.toEqual(ReflectiveKey.get(Engine));
});
it('should support overriding factory dependencies with dependency annotations', () => {
const providers = ReflectiveInjector.resolve([{
provide: 'token',
useFactory: (e: any /** TODO #9100 */) => 'result',
deps: [[new Inject('dep')]]
}]);
const provider = providers[0];
expect(provider.resolvedFactories[0].dependencies[0].key.token).toEqual('dep');
});
it('should allow declaring dependencies with flat arrays', () => {
const resolved = ReflectiveInjector.resolve(
[{provide: 'token', useFactory: (e: any) => e, deps: [new Inject('dep')]}]);
const nestedResolved = ReflectiveInjector.resolve(
[{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject('dep')]]}]);
expect(resolved[0].resolvedFactories[0].dependencies[0].key.token)
.toEqual(nestedResolved[0].resolvedFactories[0].dependencies[0].key.token);
});
});
describe('displayName', () => {
it('should work', () => {
expect((<ReflectiveInjector_>ReflectiveInjector.resolveAndCreate([Engine, BrokenEngine]))
.displayName)
.toEqual('ReflectiveInjector(providers: [ "Engine" , "BrokenEngine" ])');
});
});
})();

View File

@ -12,16 +12,20 @@ import {KeyRegistry} from '@angular/core/src/di/reflective_key';
describe('key', function() {
let registry: KeyRegistry;
beforeEach(function() { registry = new KeyRegistry(); });
beforeEach(function() {
registry = new KeyRegistry();
});
it('should be equal to another key if type is the same',
function() { expect(registry.get('car')).toBe(registry.get('car')); });
it('should be equal to another key if type is the same', function() {
expect(registry.get('car')).toBe(registry.get('car'));
});
it('should not be equal to another key if types are different',
function() { expect(registry.get('car')).not.toBe(registry.get('porsche')); });
it('should return the passed in key',
function() { expect(registry.get(registry.get('car'))).toBe(registry.get('car')); });
it('should not be equal to another key if types are different', function() {
expect(registry.get('car')).not.toBe(registry.get('porsche'));
});
it('should return the passed in key', function() {
expect(registry.get(registry.get('car'))).toBe(registry.get('car'));
});
});
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Inject, InjectFlags, InjectionToken, Injector, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
import {forwardRef, Inject, InjectFlags, InjectionToken, Injector, Optional, Self, SkipSelf} from '@angular/core';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy} from '@angular/private/testing';
@ -18,7 +18,9 @@ class Engine {
class BrokenEngine {
static PROVIDER = {provide: Engine, useClass: BrokenEngine, deps: []};
constructor() { throw new Error('Broken Engine'); }
constructor() {
throw new Error('Broken Engine');
}
}
class DashboardSoftware {
@ -75,7 +77,7 @@ class NoAnnotations {
constructor(secretDependency: any) {}
}
function factoryFn(a: any){}
function factoryFn(a: any) {}
{
const dynamicProviders = [
@ -88,7 +90,6 @@ function factoryFn(a: any){}
];
modifiedInIvy('Ivy uses R3Injector').describe(`StaticInjector`, () => {
it('should instantiate a class without dependencies', () => {
const injector = Injector.create([Engine.PROVIDER]);
const engine = injector.get(Engine);
@ -133,7 +134,9 @@ function factoryFn(a: any){}
});
it('should provide to a factory', () => {
function sportsCarFactory(e: any) { return new SportsCar(e); }
function sportsCarFactory(e: any) {
return new SportsCar(e);
}
const injector = Injector.create(
[Engine.PROVIDER, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
@ -187,26 +190,22 @@ function factoryFn(a: any){}
it('should throw when the aliased provider does not exist', () => {
const injector = Injector.create([{provide: 'car', useExisting: SportsCar}]);
const e =
`StaticInjectorError[car -> ${stringify(SportsCar)}]: \n NullInjectorError: No provider for ${stringify(SportsCar)}!`;
const e = `StaticInjectorError[car -> ${
stringify(SportsCar)}]: \n NullInjectorError: No provider for ${stringify(SportsCar)}!`;
expect(() => injector.get('car')).toThrowError(e);
});
it('should handle forwardRef in useExisting', () => {
const injector = Injector.create([
{provide: 'originalEngine', useClass: forwardRef(() => Engine), deps: []}, {
provide: 'aliasedEngine',
useExisting: <any>forwardRef(() => 'originalEngine'),
deps: []
}
{provide: 'originalEngine', useClass: forwardRef(() => Engine), deps: []},
{provide: 'aliasedEngine', useExisting: <any>forwardRef(() => 'originalEngine'), deps: []}
]);
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
});
it('should support overriding factory dependencies', () => {
const injector = Injector.create([
Engine.PROVIDER,
{provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}
Engine.PROVIDER, {provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}
]);
const car = injector.get<Car>(Car);
@ -249,9 +248,9 @@ function factoryFn(a: any){}
});
it('should throw when missing deps', () => {
expect(() => Injector.create(<any>[{provide: Engine, useClass: Engine}]))
.toThrowError(
'StaticInjectorError[{provide:Engine, useClass:Engine}]: \'deps\' required');
expect(() => Injector.create(<any>[
{provide: Engine, useClass: Engine}
])).toThrowError('StaticInjectorError[{provide:Engine, useClass:Engine}]: \'deps\' required');
});
it('should throw when using reflective API', () => {
@ -289,7 +288,8 @@ function factoryFn(a: any){}
Injector.create([CarWithDashboard.PROVIDER, Engine.PROVIDER, Dashboard.PROVIDER]);
expect(() => injector.get(CarWithDashboard))
.toThrowError(
`StaticInjectorError[${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware]: \n` +
`StaticInjectorError[${stringify(CarWithDashboard)} -> ${
stringify(Dashboard)} -> DashboardSoftware]: \n` +
' NullInjectorError: No provider for DashboardSoftware!');
});
@ -297,14 +297,21 @@ function factoryFn(a: any){}
const injector = Injector.create([Car.PROVIDER, CyclicEngine.PROVIDER]);
expect(() => injector.get(Car))
.toThrowError(
`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)}]: Circular dependency`);
.toThrowError(`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)} -> ${
stringify(Car)}]: Circular dependency`);
});
it('should show the full path when error happens in a constructor', () => {
const error = new Error('MyError');
const injector = Injector.create(
[Car.PROVIDER, {provide: Engine, useFactory: () => { throw error; }, deps: []}]);
const injector = Injector.create([
Car.PROVIDER, {
provide: Engine,
useFactory: () => {
throw error;
},
deps: []
}
]);
try {
injector.get(Car);
@ -345,7 +352,6 @@ function factoryFn(a: any){}
expect(injector.get('null')).toBe(null);
expect(injector.get('undefined')).toBe(undefined);
});
});
@ -495,8 +501,7 @@ function factoryFn(a: any){}
it('should support overriding factory dependencies with dependency annotations', () => {
const injector = Injector.create([
Engine.PROVIDER,
{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject(Engine)]]}
Engine.PROVIDER, {provide: 'token', useFactory: (e: any) => e, deps: [[new Inject(Engine)]]}
]);
expect(injector.get('token')).toBeAnInstanceOf(Engine);

View File

@ -8,7 +8,7 @@
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnInit} from '@angular/core';
import {Component, Directive} from '@angular/core/src/metadata';
import {TestBed, inject} from '@angular/core/testing';
import {inject, TestBed} from '@angular/core/testing';
import {Log} from '@angular/core/testing/src/testing_internal';
describe('directive lifecycle integration spec', () => {
@ -27,7 +27,9 @@ describe('directive lifecycle integration spec', () => {
.overrideComponent(MyComp5, {set: {template: '<div [field]="123" lifecycle></div>'}});
});
beforeEach(inject([Log], (_log: any) => { log = _log; }));
beforeEach(inject([Log], (_log: any) => {
log = _log;
}));
it('should invoke lifecycle methods ngOnChanges > ngOnInit > ngDoCheck > ngAfterContentChecked',
() => {
@ -51,7 +53,9 @@ describe('directive lifecycle integration spec', () => {
@Directive({selector: '[lifecycle-dir]'})
class LifecycleDir implements DoCheck {
constructor(private _log: Log) {}
ngDoCheck() { this._log.add('child_ngDoCheck'); }
ngDoCheck() {
this._log.add('child_ngDoCheck');
}
}
@Component({
@ -59,24 +63,38 @@ class LifecycleDir implements DoCheck {
inputs: ['field'],
template: `<div lifecycle-dir></div>`,
})
class LifecycleCmp implements OnChanges,
OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked {
class LifecycleCmp implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked {
field: any /** TODO #9100 */;
constructor(private _log: Log) {}
ngOnChanges(_: any /** TODO #9100 */) { this._log.add('ngOnChanges'); }
ngOnChanges(_: any /** TODO #9100 */) {
this._log.add('ngOnChanges');
}
ngOnInit() { this._log.add('ngOnInit'); }
ngOnInit() {
this._log.add('ngOnInit');
}
ngDoCheck() { this._log.add('ngDoCheck'); }
ngDoCheck() {
this._log.add('ngDoCheck');
}
ngAfterContentInit() { this._log.add('ngAfterContentInit'); }
ngAfterContentInit() {
this._log.add('ngAfterContentInit');
}
ngAfterContentChecked() { this._log.add('ngAfterContentChecked'); }
ngAfterContentChecked() {
this._log.add('ngAfterContentChecked');
}
ngAfterViewInit() { this._log.add('ngAfterViewInit'); }
ngAfterViewInit() {
this._log.add('ngAfterViewInit');
}
ngAfterViewChecked() { this._log.add('ngAfterViewChecked'); }
ngAfterViewChecked() {
this._log.add('ngAfterViewChecked');
}
}
@Component({selector: 'my-comp'})

View File

@ -40,8 +40,9 @@ import {isTextNode} from '@angular/platform-browser/testing/src/browser_util';
describe('getBaseHref', () => {
beforeEach(() => getDOM().resetBaseElement());
it('should return null if base element is absent',
() => { expect(getDOM().getBaseHref(defaultDoc)).toBeNull(); });
it('should return null if base element is absent', () => {
expect(getDOM().getBaseHref(defaultDoc)).toBeNull();
});
it('should return the value of the base element', () => {
const baseEl = getDOM().createElement('base');
@ -62,7 +63,7 @@ import {isTextNode} from '@angular/platform-browser/testing/src/browser_util';
const headEl = defaultDoc.head;
headEl.appendChild(baseEl);
const baseHref = getDOM().getBaseHref(defaultDoc) !;
const baseHref = getDOM().getBaseHref(defaultDoc)!;
headEl.removeChild(baseEl);
getDOM().resetBaseElement();
@ -70,7 +71,5 @@ import {isTextNode} from '@angular/platform-browser/testing/src/browser_util';
});
});
}
});
}

View File

@ -10,7 +10,6 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
{
describe('Shim', () => {
it('should provide correct function.name ', () => {
const functionWithoutName = identity(() => function(_: any /** TODO #9100 */) {});
function foo(_: any /** TODO #9100 */) {}
@ -18,7 +17,6 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
expect((<any>functionWithoutName).name).toBeFalsy();
expect((<any>foo).name).toEqual('foo');
});
});
}

View File

@ -12,66 +12,73 @@ import {ErrorHandler} from '../src/error_handler';
class MockConsole {
res: any[][] = [];
error(...s: any[]): void { this.res.push(s); }
error(...s: any[]): void {
this.res.push(s);
}
}
(function() {
function errorToString(error: any) {
const logger = new MockConsole();
const errorHandler = new ErrorHandler();
(errorHandler as any)._console = logger as any;
errorHandler.handleError(error);
return logger.res.map(line => line.join('#')).join('\n');
}
function errorToString(error: any) {
const logger = new MockConsole();
const errorHandler = new ErrorHandler();
(errorHandler as any)._console = logger as any;
errorHandler.handleError(error);
return logger.res.map(line => line.join('#')).join('\n');
}
describe('ErrorHandler', () => {
it('should output exception', () => {
const e = errorToString(new Error('message!'));
expect(e).toContain('message!');
});
describe('ErrorHandler', () => {
it('should output exception', () => {
const e = errorToString(new Error('message!'));
expect(e).toContain('message!');
});
describe('context', () => {
it('should print nested context', () => {
const cause = new Error('message!');
const context = { source: 'context!', toString() { return 'Context'; } } as any;
const original = debugError(cause, context);
const e = errorToString(wrappedError('message', original));
expect(e).toEqual(`ERROR#Error: message caused by: Error in context! caused by: message!
describe('context', () => {
it('should print nested context', () => {
const cause = new Error('message!');
const context = {
source: 'context!',
toString() {
return 'Context';
}
} as any;
const original = debugError(cause, context);
const e = errorToString(wrappedError('message', original));
expect(e).toEqual(`ERROR#Error: message caused by: Error in context! caused by: message!
ORIGINAL ERROR#Error: message!
ERROR CONTEXT#Context`);
});
});
describe('original exception', () => {
it('should print original exception message if available (original is Error)', () => {
const realOriginal = new Error('inner');
const original = wrappedError('wrapped', realOriginal);
const e = errorToString(wrappedError('wrappedwrapped', original));
expect(e).toContain('inner');
});
it('should print original exception message if available (original is not Error)', () => {
const realOriginal = new Error('custom');
const original = wrappedError('wrapped', realOriginal);
const e = errorToString(wrappedError('wrappedwrapped', original));
expect(e).toContain('custom');
});
});
it('should use the error logger on the error', () => {
const err = new Error('test');
const console = new MockConsole();
const errorHandler = new ErrorHandler();
(errorHandler as any)._console = console as any;
const logger = jasmine.createSpy('logger');
(err as any)[ERROR_LOGGER] = logger;
errorHandler.handleError(err);
expect(console.res).toEqual([]);
expect(logger).toHaveBeenCalledWith(console, 'ERROR', err);
});
});
describe('original exception', () => {
it('should print original exception message if available (original is Error)', () => {
const realOriginal = new Error('inner');
const original = wrappedError('wrapped', realOriginal);
const e = errorToString(wrappedError('wrappedwrapped', original));
expect(e).toContain('inner');
});
it('should print original exception message if available (original is not Error)', () => {
const realOriginal = new Error('custom');
const original = wrappedError('wrapped', realOriginal);
const e = errorToString(wrappedError('wrappedwrapped', original));
expect(e).toContain('custom');
});
});
it('should use the error logger on the error', () => {
const err = new Error('test');
const console = new MockConsole();
const errorHandler = new ErrorHandler();
(errorHandler as any)._console = console as any;
const logger = jasmine.createSpy('logger');
(err as any)[ERROR_LOGGER] = logger;
errorHandler.handleError(err);
expect(console.res).toEqual([]);
expect(logger).toHaveBeenCalledWith(console, 'ERROR', err);
});
});
})();
function debugError(originalError: any, context: any): Error {

View File

@ -15,7 +15,9 @@ import {EventEmitter} from '../src/event_emitter';
describe('EventEmitter', () => {
let emitter: EventEmitter<any>;
beforeEach(() => { emitter = new EventEmitter(); });
beforeEach(() => {
emitter = new EventEmitter();
});
it('should call the next callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
@ -42,20 +44,34 @@ import {EventEmitter} from '../src/event_emitter';
it('should work when no throw callback is provided',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
emitter.subscribe({next: () => {}, error: (_: any) => { async.done(); }});
emitter.subscribe({
next: () => {},
error: (_: any) => {
async.done();
}
});
emitter.error('Boom');
}));
it('should call the return callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
emitter.subscribe(
{next: () => {}, error: (_: any) => {}, complete: () => { async.done(); }});
emitter.subscribe({
next: () => {},
error: (_: any) => {},
complete: () => {
async.done();
}
});
emitter.complete();
}));
it('should subscribe to the wrapper synchronously', () => {
let called = false;
emitter.subscribe({next: (value: any) => { called = true; }});
emitter.subscribe({
next: (value: any) => {
called = true;
}
});
emitter.emit(99);
expect(called).toBe(true);
@ -117,7 +133,6 @@ import {EventEmitter} from '../src/event_emitter';
log.push(1);
e.emit(2);
log.push(3);
}));
it('reports whether it has subscribers', () => {
@ -161,8 +176,16 @@ import {EventEmitter} from '../src/event_emitter';
it('error thrown inside an Rx chain propagates to the error handler and disposes the chain',
() => {
let errorPropagated = false;
emitter.pipe(filter(() => { throw new Error(); }), )
.subscribe(() => {}, err => errorPropagated = true, );
emitter
.pipe(
filter(() => {
throw new Error();
}),
)
.subscribe(
() => {},
err => errorPropagated = true,
);
emitter.next(1);
@ -172,7 +195,11 @@ import {EventEmitter} from '../src/event_emitter';
it('error sent by EventEmitter should dispose the Rx chain and remove subscribers', () => {
let errorPropagated = false;
emitter.pipe(filter(() => true)).subscribe(() => {}, err => errorPropagated = true, );
emitter.pipe(filter(() => true))
.subscribe(
() => {},
err => errorPropagated = true,
);
emitter.error(1);

View File

@ -7,7 +7,7 @@
*/
import {discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, tick} from '@angular/core/testing';
import {Log, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
import {beforeEach, describe, inject, it, Log} from '@angular/core/testing/src/testing_internal';
import {EventManager} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -18,7 +18,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
describe('fake async', () => {
it('should run synchronous code', () => {
let ran = false;
fakeAsync(() => { ran = true; })();
fakeAsync(() => {
ran = true;
})();
expect(ran).toEqual(true);
});
@ -37,26 +39,35 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should throw on nested calls', () => {
expect(() => {
fakeAsync(() => { fakeAsync((): any /** TODO #9100 */ => null)(); })();
fakeAsync(() => {
fakeAsync((): any /** TODO #9100 */ => null)();
})();
}).toThrowError('fakeAsync() calls can not be nested');
});
it('should flush microtasks before returning', () => {
let thenRan = false;
fakeAsync(() => { resolvedPromise.then(_ => { thenRan = true; }); })();
fakeAsync(() => {
resolvedPromise.then(_ => {
thenRan = true;
});
})();
expect(thenRan).toEqual(true);
});
it('should propagate the return value',
() => { expect(fakeAsync(() => 'foo')()).toEqual('foo'); });
it('should propagate the return value', () => {
expect(fakeAsync(() => 'foo')()).toEqual('foo');
});
describe('Promise', () => {
it('should run asynchronous code', fakeAsync(() => {
let thenRan = false;
resolvedPromise.then((_) => { thenRan = true; });
resolvedPromise.then((_) => {
thenRan = true;
});
expect(thenRan).toEqual(false);
@ -92,22 +103,29 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should complain if the test throws an exception during async calls', () => {
expect(() => {
fakeAsync(() => {
resolvedPromise.then((_) => { throw new Error('async'); });
resolvedPromise.then((_) => {
throw new Error('async');
});
flushMicrotasks();
})();
}).toThrow();
});
it('should complain if a test throws an exception', () => {
expect(() => { fakeAsync(() => { throw new Error('sync'); })(); }).toThrowError('sync');
expect(() => {
fakeAsync(() => {
throw new Error('sync');
})();
}).toThrowError('sync');
});
});
describe('timers', () => {
it('should run queued zero duration timer on zero tick', fakeAsync(() => {
let ran = false;
setTimeout(() => { ran = true; }, 0);
setTimeout(() => {
ran = true;
}, 0);
expect(ran).toEqual(false);
@ -118,7 +136,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should run queued timer after sufficient clock ticks', fakeAsync(() => {
let ran = false;
setTimeout(() => { ran = true; }, 10);
setTimeout(() => {
ran = true;
}, 10);
tick(6);
expect(ran).toEqual(false);
@ -154,7 +174,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should run queued timer only once', fakeAsync(() => {
let cycles = 0;
setTimeout(() => { cycles++; }, 10);
setTimeout(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
@ -168,7 +190,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should not run cancelled timer', fakeAsync(() => {
let ran = false;
const id = setTimeout(() => { ran = true; }, 10);
const id = setTimeout(() => {
ran = true;
}, 10);
clearTimeout(id);
tick(10);
@ -177,19 +201,25 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should throw an error on dangling timers', () => {
expect(() => {
fakeAsync(() => { setTimeout(() => {}, 10); })();
fakeAsync(() => {
setTimeout(() => {}, 10);
})();
}).toThrowError('1 timer(s) still in the queue.');
});
it('should throw an error on dangling periodic timers', () => {
expect(() => {
fakeAsync(() => { setInterval(() => {}, 10); })();
fakeAsync(() => {
setInterval(() => {}, 10);
})();
}).toThrowError('1 periodic timer(s) still in the queue.');
});
it('should run periodic timers', fakeAsync(() => {
let cycles = 0;
const id = setInterval(() => { cycles++; }, 10);
const id = setInterval(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
@ -204,7 +234,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should not run cancelled periodic timer', fakeAsync(() => {
let ran = false;
const id = setInterval(() => { ran = true; }, 10);
const id = setInterval(() => {
ran = true;
}, 10);
clearInterval(id);
tick(10);
@ -229,7 +261,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should clear periodic timers', fakeAsync(() => {
let cycles = 0;
const id = setInterval(() => { cycles++; }, 10);
const id = setInterval(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
@ -289,7 +323,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should flush tasks', fakeAsync(() => {
let ran = false;
setTimeout(() => { ran = true; }, 10);
setTimeout(() => {
ran = true;
}, 10);
flush();
expect(ran).toEqual(true);
@ -298,8 +334,12 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should flush multiple tasks', fakeAsync(() => {
let ran = false;
let ran2 = false;
setTimeout(() => { ran = true; }, 10);
setTimeout(() => { ran2 = true; }, 30);
setTimeout(() => {
ran = true;
}, 10);
setTimeout(() => {
ran2 = true;
}, 30);
let elapsed = flush();
@ -311,8 +351,12 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
it('should move periodic tasks', fakeAsync(() => {
let ran = false;
let count = 0;
setInterval(() => { count++; }, 10);
setTimeout(() => { ran = true; }, 35);
setInterval(() => {
count++;
}, 10);
setTimeout(() => {
ran = true;
}, 35);
let elapsed = flush();
@ -353,7 +397,9 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
describe('only one `fakeAsync` zone per test', () => {
let zoneInBeforeEach: Zone;
let zoneInTest1: Zone;
beforeEach(fakeAsync(() => { zoneInBeforeEach = Zone.current; }));
beforeEach(fakeAsync(() => {
zoneInBeforeEach = Zone.current;
}));
it('should use the same zone as in beforeEach', fakeAsync(() => {
zoneInTest1 = Zone.current;
@ -363,9 +409,13 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
});
describe('ProxyZone', () => {
beforeEach(() => { ProxyZoneSpec.assertPresent(); });
beforeEach(() => {
ProxyZoneSpec.assertPresent();
});
afterEach(() => { ProxyZoneSpec.assertPresent(); });
afterEach(() => {
ProxyZoneSpec.assertPresent();
});
it('should allow fakeAsync zone to retroactively set a zoneSpec outside of fakeAsync', () => {
ProxyZoneSpec.assertPresent();

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component, ContentChildren, Directive, Inject, NO_ERRORS_SCHEMA, NgModule, QueryList, asNativeElements, forwardRef} from '@angular/core';
import {asNativeElements, Component, ContentChildren, Directive, forwardRef, Inject, NgModule, NO_ERRORS_SCHEMA, QueryList} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -20,7 +20,9 @@ class ModuleFrame {
}
describe('forwardRef integration', function() {
beforeEach(() => { TestBed.configureTestingModule({imports: [Module], declarations: [App]}); });
beforeEach(() => {
TestBed.configureTestingModule({imports: [Module], declarations: [App]});
});
it('should instantiate components which are declared using forwardRef', () => {
const a = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}).createComponent(App);
@ -53,10 +55,12 @@ class App {
})
class Door {
// TODO(issue/24571): remove '!'.
@ContentChildren(forwardRef(() => Lock)) locks !: QueryList<Lock>;
@ContentChildren(forwardRef(() => Lock)) locks!: QueryList<Lock>;
frame: Frame;
constructor(@Inject(forwardRef(() => Frame)) frame: Frame) { this.frame = frame; }
constructor(@Inject(forwardRef(() => Frame)) frame: Frame) {
this.frame = frame;
}
}
@Directive({selector: 'lock'})

View File

@ -5,7 +5,7 @@
* 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 {LocaleDataIndex, findLocaleData, getLocaleCurrencyCode, registerLocaleData, unregisterAllLocaleData} from '../../src/i18n/locale_data_api';
import {findLocaleData, getLocaleCurrencyCode, LocaleDataIndex, registerLocaleData, unregisterAllLocaleData} from '../../src/i18n/locale_data_api';
import {global} from '../../src/util/global';
{
@ -53,14 +53,18 @@ import {global} from '../../src/util/global';
.toThrowError(/Missing locale data for the locale "pt-AO"/);
});
it('should return english data if the locale is en-US',
() => { expect(findLocaleData('en-US')).toEqual(localeEn); });
it('should return english data if the locale is en-US', () => {
expect(findLocaleData('en-US')).toEqual(localeEn);
});
it('should return the exact LOCALE_DATA if it is available',
() => { expect(findLocaleData('fr-CA')).toEqual(localeFrCA); });
it('should return the exact LOCALE_DATA if it is available', () => {
expect(findLocaleData('fr-CA')).toEqual(localeFrCA);
});
it('should return the parent LOCALE_DATA if it exists and exact locale is not available',
() => { expect(findLocaleData('fr-BE')).toEqual(localeFr); });
() => {
expect(findLocaleData('fr-BE')).toEqual(localeFr);
});
it(`should find the LOCALE_DATA even if the locale id is badly formatted`, () => {
expect(findLocaleData('ca-ES-VALENCIA')).toEqual(localeCaESVALENCIA);
@ -73,22 +77,30 @@ import {global} from '../../src/util/global';
expect(findLocaleData('fake-id2')).toEqual(localeFrCA);
});
it('should find the exact LOCALE_DATA if the locale is on the global object',
() => { expect(findLocaleData('de-CH')).toEqual(localeDeCH); });
it('should find the exact LOCALE_DATA if the locale is on the global object', () => {
expect(findLocaleData('de-CH')).toEqual(localeDeCH);
});
it('should find the parent LOCALE_DATA if the exact locale is not available and the parent locale is on the global object',
() => { expect(findLocaleData('de-BE')).toEqual(localeDe); });
() => {
expect(findLocaleData('de-BE')).toEqual(localeDe);
});
it('should find the registered LOCALE_DATA even if the same locale is on the global object',
() => { expect(findLocaleData('fr')).not.toBe(fakeGlobalFr); });
() => {
expect(findLocaleData('fr')).not.toBe(fakeGlobalFr);
});
});
describe('getLocaleCurrencyCode()', () => {
it('should return `null` if the given locale does not provide a currency code',
() => { expect(getLocaleCurrencyCode('de')).toBe(null); });
it('should return `null` if the given locale does not provide a currency code', () => {
expect(getLocaleCurrencyCode('de')).toBe(null);
});
it('should return the code at the `LocaleDataIndex.CurrencyCode` of the given locale`s data',
() => { expect(getLocaleCurrencyCode('fr')).toEqual('EUR'); });
() => {
expect(getLocaleCurrencyCode('fr')).toEqual('EUR');
});
});
});
}

View File

@ -14,17 +14,25 @@ import {obsoleteInIvy} from '@angular/private/testing';
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
class DummyConsole implements Console {
public warnings: string[] = [];
log(message: string) {}
warn(message: string) { this.warnings.push(message); }
warn(message: string) {
this.warnings.push(message);
}
}
function declareTests(config?: {useJit: boolean}) {
@ -40,7 +48,7 @@ function declareTests(config?: {useJit: boolean}) {
const compFixture = TestBed.createComponent(MainComp);
const mainComp: MainComp = compFixture.componentInstance;
expect(compFixture.componentRef.injector.get(ComponentFactoryResolver)).toBe(mainComp.cfr);
const cf = mainComp.cfr.resolveComponentFactory(ChildComp) !;
const cf = mainComp.cfr.resolveComponentFactory(ChildComp)!;
expect(cf.componentType).toBe(ChildComp);
});
@ -52,8 +60,8 @@ function declareTests(config?: {useJit: boolean}) {
const mainComp: CompWithAnalyzeEntryComponentsProvider = compFixture.componentInstance;
const cfr: ComponentFactoryResolver =
compFixture.componentRef.injector.get(ComponentFactoryResolver);
expect(cfr.resolveComponentFactory(ChildComp) !.componentType).toBe(ChildComp);
expect(cfr.resolveComponentFactory(NestedChildComp) !.componentType).toBe(NestedChildComp);
expect(cfr.resolveComponentFactory(ChildComp)!.componentType).toBe(ChildComp);
expect(cfr.resolveComponentFactory(NestedChildComp)!.componentType).toBe(NestedChildComp);
});
it('should be able to get a component form a parent component (view hierarchy)', () => {
@ -63,10 +71,10 @@ function declareTests(config?: {useJit: boolean}) {
const childCompEl = compFixture.debugElement.children[0];
const childComp: ChildComp = childCompEl.componentInstance;
// declared on ChildComp directly
expect(childComp.cfr.resolveComponentFactory(NestedChildComp) !.componentType)
expect(childComp.cfr.resolveComponentFactory(NestedChildComp)!.componentType)
.toBe(NestedChildComp);
// inherited from MainComp
expect(childComp.cfr.resolveComponentFactory(ChildComp) !.componentType).toBe(ChildComp);
expect(childComp.cfr.resolveComponentFactory(ChildComp)!.componentType).toBe(ChildComp);
});
obsoleteInIvy('In Ivy, the ComponentFactoryResolver can resolve any component factory')
@ -79,12 +87,11 @@ function declareTests(config?: {useJit: boolean}) {
const compFixture = TestBed.createComponent(MainComp);
const nestedChildCompEl = compFixture.debugElement.children[0].children[0];
const nestedChildComp: NestedChildComp = nestedChildCompEl.componentInstance;
expect(nestedChildComp.cfr.resolveComponentFactory(ChildComp) !.componentType)
expect(nestedChildComp.cfr.resolveComponentFactory(ChildComp)!.componentType)
.toBe(ChildComp);
expect(() => nestedChildComp.cfr.resolveComponentFactory(NestedChildComp))
.toThrow(noComponentFactoryError(NestedChildComp));
});
});
}

View File

@ -17,8 +17,7 @@ class DirectiveA {
@Directive({selector: '[directiveB]'})
class DirectiveB {
@HostBinding('title')
title = 'DirectiveB Title';
@HostBinding('title') title = 'DirectiveB Title';
}
@Component({selector: 'component-a', template: 'ComponentA Template'})
@ -34,8 +33,7 @@ class ComponentWithNoAnnotation extends ComponentA {}
@Directive({selector: '[directiveExtendsComponent]'})
class DirectiveExtendsComponent extends ComponentA {
@HostBinding('title')
title = 'DirectiveExtendsComponent Title';
@HostBinding('title') title = 'DirectiveExtendsComponent Title';
}
class DirectiveWithNoAnnotation extends DirectiveB {}

View File

@ -7,7 +7,7 @@
*/
import {CommonModule, DOCUMENT, ɵgetDOM as getDOM} from '@angular/common';
import {Compiler, ComponentFactory, ComponentRef, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, SkipSelf, ViewRef, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {Compiler, ComponentFactory, ComponentRef, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, OnDestroy, SkipSelf, ViewRef, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
import {getDebugContext} from '@angular/core/src/errors';
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@ -17,7 +17,7 @@ import {TemplateRef} from '@angular/core/src/linker/template_ref';
import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref';
import {EmbeddedViewRef} from '@angular/core/src/linker/view_ref';
import {Attribute, Component, ContentChildren, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata';
import {TestBed, async, fakeAsync, getTestBed, tick} from '@angular/core/testing';
import {async, fakeAsync, getTestBed, TestBed, tick} from '@angular/core/testing';
import {createMouseEvent, dispatchEvent, el, isCommentNode} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@ -27,16 +27,23 @@ import {stringify} from '../../src/util/stringify';
const ANCHOR_ELEMENT = new InjectionToken('AnchorElement');
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
function declareTests(config?: {useJit: boolean}) {
describe('integration tests', function() {
beforeEach(() => { TestBed.configureCompiler({...config}); });
beforeEach(() => {
TestBed.configureCompiler({...config});
});
describe('react to record changes', function() {
it('should consume text node changes', () => {
@ -55,7 +62,7 @@ function declareTests(config?: {useJit: boolean}) {
const template = '<div>{{null}}{{ctxProp}}</div>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = null !;
fixture.componentInstance.ctxProp = null!;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
@ -133,7 +140,7 @@ function declareTests(config?: {useJit: boolean}) {
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.getAttribute('foo')).toEqual('bar');
fixture.componentInstance.ctxProp = null !;
fixture.componentInstance.ctxProp = null!;
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.hasAttribute('foo')).toBeFalsy();
});
@ -148,7 +155,7 @@ function declareTests(config?: {useJit: boolean}) {
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.style['height']).toEqual('10px');
fixture.componentInstance.ctxProp = null !;
fixture.componentInstance.ctxProp = null!;
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.style['height']).toEqual('');
});
@ -265,7 +272,7 @@ function declareTests(config?: {useJit: boolean}) {
fixture.componentInstance.ctxProp = 'a';
fixture.detectChanges();
const dir = fixture.debugElement.children[0].references !['dir'];
const dir = fixture.debugElement.children[0].references!['dir'];
expect(dir.dirProp).toEqual('aa');
});
});
@ -344,7 +351,7 @@ function declareTests(config?: {useJit: boolean}) {
it('should display correct error message for uninitialized @Output', () => {
@Component({selector: 'my-uninitialized-output', template: '<p>It works!</p>'})
class UninitializedOutputComp {
@Output() customEvent !: EventEmitter<any>;
@Output() customEvent!: EventEmitter<any>;
}
const template =
@ -406,7 +413,7 @@ function declareTests(config?: {useJit: boolean}) {
ngIfEl.childNodes
.find(
debugElement => debugElement.nativeNode.nodeType ===
Node.COMMENT_NODE) !.injector.get(SomeViewport);
Node.COMMENT_NODE)!.injector.get(SomeViewport);
expect(someViewport.container.length).toBe(2);
expect(ngIfEl.children.length).toBe(2);
@ -455,7 +462,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].children[0].references !['alice'])
expect(fixture.debugElement.children[0].children[0].references!['alice'])
.toBeAnInstanceOf(ChildComp);
});
@ -465,7 +472,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].children[0].references !['localdir'])
expect(fixture.debugElement.children[0].children[0].references!['localdir'])
.toBeAnInstanceOf(ExportDir);
});
@ -477,9 +484,9 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].references !['x'])
expect(fixture.debugElement.children[0].references!['x'])
.toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
expect(fixture.debugElement.children[0].references !['y'])
expect(fixture.debugElement.children[0].references!['y'])
.toBeAnInstanceOf(DirectiveWithMultipleExportAsNames);
});
@ -505,8 +512,8 @@ function declareTests(config?: {useJit: boolean}) {
const pEl = fixture.debugElement.children[0];
const alice = pEl.children[0].references !['alice'];
const bob = pEl.children[1].references !['bob'];
const alice = pEl.children[0].references!['alice'];
const bob = pEl.children[1].references!['bob'];
expect(alice).toBeAnInstanceOf(ChildComp);
expect(bob).toBeAnInstanceOf(ChildComp);
expect(alice).not.toBe(bob);
@ -518,8 +525,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].references !['alice'])
.toBeAnInstanceOf(ChildComp);
expect(fixture.debugElement.children[0].references!['alice']).toBeAnInstanceOf(ChildComp);
});
it('should assign the element instance to a user-defined variable', () => {
@ -528,7 +534,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const value = fixture.debugElement.children[0].children[0].references !['alice'];
const value = fixture.debugElement.children[0].children[0].references!['alice'];
expect(value).not.toBe(null);
expect(value.tagName.toLowerCase()).toEqual('div');
});
@ -540,7 +546,7 @@ function declareTests(config?: {useJit: boolean}) {
MyComp, {set: {template: '<ng-template ref-alice></ng-template>'}})
.createComponent(MyComp);
const value = fixture.debugElement.childNodes[0].references !['alice'];
const value = fixture.debugElement.childNodes[0].references!['alice'];
expect(value.createEmbeddedView).toBeTruthy();
});
@ -550,7 +556,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
expect(fixture.debugElement.children[0].children[0].references !['superAlice'])
expect(fixture.debugElement.children[0].children[0].references!['superAlice'])
.toBeAnInstanceOf(ChildComp);
});
});
@ -573,14 +579,13 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('OnPush components', () => {
it('should use ChangeDetectorRef to manually request a check', () => {
TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const cmp = fixture.debugElement.children[0].references !['cmp'];
const cmp = fixture.debugElement.children[0].references!['cmp'];
fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
@ -601,7 +606,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const cmp = fixture.debugElement.children[0].references !['cmp'];
const cmp = fixture.debugElement.children[0].references!['cmp'];
fixture.componentInstance.ctxProp = 'one';
fixture.detectChanges();
@ -675,7 +680,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const cmp = fixture.debugElement.children[0].references !['cmp'];
const cmp = fixture.debugElement.children[0].references!['cmp'];
fixture.componentInstance.ctxProp = 'one';
fixture.detectChanges();
@ -695,7 +700,7 @@ function declareTests(config?: {useJit: boolean}) {
tick();
const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references !['cmp'];
const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references!['cmp'];
fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
@ -726,7 +731,7 @@ function declareTests(config?: {useJit: boolean}) {
const fixture = TestBed.createComponent(MyComp);
const childComponent =
fixture.debugElement.children[0].children[0].children[0].references !['child'];
fixture.debugElement.children[0].children[0].children[0].references!['child'];
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
});
@ -748,7 +753,7 @@ function declareTests(config?: {useJit: boolean}) {
const tc = fixture.debugElement.children[0].children[0].children[0];
const childComponent = tc.references !['child'];
const childComponent = tc.references!['child'];
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
});
@ -795,7 +800,7 @@ function declareTests(config?: {useJit: boolean}) {
})
.createComponent(MyComp);
const tc = fixture.debugElement.childNodes.find(
debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE) !;
debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE)!;
const emitter = tc.injector.get(DirectiveEmittingEvent);
const myComp = fixture.debugElement.injector.get(MyComp);
@ -827,8 +832,11 @@ function declareTests(config?: {useJit: boolean}) {
expect(dir.control).toEqual('one');
dir.controlChange.subscribe(
{next: () => { expect(fixture.componentInstance.ctxProp).toEqual('two'); }});
dir.controlChange.subscribe({
next: () => {
expect(fixture.componentInstance.ctxProp).toEqual('two');
}
});
dir.triggerChange('two');
}));
@ -954,9 +962,11 @@ function declareTests(config?: {useJit: boolean}) {
class DirectiveWithHostListener {
id = 'one';
// TODO(issue/24571): remove '!'.
receivedArgs !: any[];
receivedArgs!: any[];
doIt(...args: any[]) { this.receivedArgs = args; }
doIt(...args: any[]) {
this.receivedArgs = args;
}
}
const fixture =
@ -1239,7 +1249,6 @@ function declareTests(config?: {useJit: boolean}) {
}).toThrowError('Cannot move a destroyed View in a ViewContainer!');
}));
});
});
it('should support static attributes', () => {
@ -1292,7 +1301,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const comp = fixture.debugElement.children[0].children[0].references !['consuming'];
const comp = fixture.debugElement.children[0].children[0].references!['consuming'];
expect(comp.injectable).toBeAnInstanceOf(InjectableService);
});
@ -1308,7 +1317,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(DirectiveProvidingInjectableInView, {set: {template}});
const fixture = TestBed.createComponent(DirectiveProvidingInjectableInView);
const comp = fixture.debugElement.children[0].references !['consuming'];
const comp = fixture.debugElement.children[0].references!['consuming'];
expect(comp.injectable).toBeAnInstanceOf(InjectableService);
});
@ -1336,7 +1345,7 @@ function declareTests(config?: {useJit: boolean}) {
});
const fixture = TestBed.createComponent(MyComp);
const comp = fixture.debugElement.children[0].children[0].references !['dir'];
const comp = fixture.debugElement.children[0].children[0].references!['dir'];
expect(comp.directive.injectable).toBeAnInstanceOf(InjectableService);
});
@ -1386,7 +1395,7 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const providing = fixture.debugElement.children[0].references !['providing'];
const providing = fixture.debugElement.children[0].references!['providing'];
expect(providing.created).toBe(false);
fixture.componentInstance.ctxBoolProp = true;
@ -1469,7 +1478,7 @@ function declareTests(config?: {useJit: boolean}) {
});
it('should use a default element name for components without selectors', () => {
let noSelectorComponentFactory: ComponentFactory<SomeComponent> = undefined !;
let noSelectorComponentFactory: ComponentFactory<SomeComponent> = undefined!;
@Component({template: '----'})
class NoSelectorComponent {
@ -1480,7 +1489,7 @@ function declareTests(config?: {useJit: boolean}) {
constructor(componentFactoryResolver: ComponentFactoryResolver) {
// grab its own component factory
noSelectorComponentFactory =
componentFactoryResolver.resolveComponentFactory(NoSelectorComponent) !;
componentFactoryResolver.resolveComponentFactory(NoSelectorComponent)!;
}
}
@ -1502,8 +1511,9 @@ function declareTests(config?: {useJit: boolean}) {
TestBed.configureTestingModule({declarations: [MyComp, SomeDirectiveMissingAnnotation]});
expect(() => TestBed.createComponent(MyComp))
.toThrowError(
`Unexpected value '${stringify(SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`);
.toThrowError(`Unexpected value '${
stringify(
SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`);
});
it('should report a meaningful error when a component is missing view annotation', () => {
@ -1822,7 +1832,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
fixture.componentInstance.ctxProp = undefined !;
fixture.componentInstance.ctxProp = undefined!;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
@ -1839,7 +1849,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
fixture.componentInstance.ctxProp = null !;
fixture.componentInstance.ctxProp = null!;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
@ -1871,7 +1881,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": "true"');
fixture.componentInstance.ctxBoolProp = undefined !;
fixture.componentInstance.ctxBoolProp = undefined!;
fixture.detectChanges();
html = fixture.nativeElement.innerHTML;
@ -1893,14 +1903,13 @@ function declareTests(config?: {useJit: boolean}) {
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": "true"');
fixture.componentInstance.ctxBoolProp = null !;
fixture.componentInstance.ctxBoolProp = null!;
fixture.detectChanges();
html = fixture.nativeElement.innerHTML;
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": null');
});
});
describe('property decorators', () => {
@ -2079,7 +2088,6 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('attributes', () => {
it('should support attributes with namespace', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
const template = '<svg:use xlink:href="#id" />';
@ -2142,7 +2150,9 @@ class ComponentWithCustomInterpolationB {
@Injectable()
class MyService {
greeting: string;
constructor() { this.greeting = 'hello'; }
constructor() {
this.greeting = 'hello';
}
}
@Component({selector: 'simple-imp-cmp', template: ''})
@ -2165,14 +2175,16 @@ class DynamicViewport {
this.injector = Injector.create([{provide: MyService, useValue: myService}], vc.injector);
this.componentFactory =
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService) !;
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService)!;
}
create(): ComponentRef<ChildCompUsingService> {
return this.vc.createComponent(this.componentFactory, this.vc.length, this.injector);
}
insert(viewRef: ViewRef, index?: number): ViewRef { return this.vc.insert(viewRef, index); }
insert(viewRef: ViewRef, index?: number): ViewRef {
return this.vc.insert(viewRef, index);
}
move(viewRef: ViewRef, currentIndex: number): ViewRef {
return this.vc.move(viewRef, currentIndex);
@ -2182,25 +2194,29 @@ class DynamicViewport {
@Directive({selector: '[my-dir]', inputs: ['dirProp: elprop'], exportAs: 'mydir'})
class MyDir {
dirProp: string;
constructor() { this.dirProp = ''; }
constructor() {
this.dirProp = '';
}
}
@Directive({selector: '[my-dir2]', inputs: ['dirProp2: elprop'], exportAs: 'mydir2'})
class MyDir2 {
dirProp2: string;
constructor() { this.dirProp2 = ''; }
constructor() {
this.dirProp2 = '';
}
}
@Directive({selector: '[title]', inputs: ['title']})
class DirectiveWithTitle {
// TODO(issue/24571): remove '!'.
title !: string;
title!: string;
}
@Directive({selector: '[title]', inputs: ['title'], host: {'[title]': 'title'}})
class DirectiveWithTitleAndHostProperty {
// TODO(issue/24571): remove '!'.
title !: string;
title!: string;
}
@Component({selector: 'event-cmp', template: '<div (click)="noop()"></div>'})
@ -2220,7 +2236,9 @@ class PushCmp {
numberOfChecks: number;
prop: any;
constructor() { this.numberOfChecks = 0; }
constructor() {
this.numberOfChecks = 0;
}
noop() {}
@ -2251,7 +2269,9 @@ class PushCmpWithRef {
return 'fixed';
}
propagate() { this.ref.markForCheck(); }
propagate() {
this.ref.markForCheck();
}
}
@Component({
@ -2272,11 +2292,13 @@ class PushCmpWithHostEvent {
class PushCmpWithAsyncPipe {
numberOfChecks: number = 0;
// TODO(issue/24571): remove '!'.
resolve !: (result: any) => void;
resolve!: (result: any) => void;
promise: Promise<any>;
constructor() {
this.promise = new Promise((resolve) => { this.resolve = resolve; });
this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
}
get field() {
@ -2291,7 +2313,11 @@ class MyComp {
ctxNumProp: number;
ctxBoolProp: boolean;
ctxArrProp: number[];
toStringThrow = {toString: function() { throw 'boom'; }};
toStringThrow = {
toString: function() {
throw 'boom';
}
};
constructor() {
this.ctxProp = 'initial value';
@ -2300,7 +2326,9 @@ class MyComp {
this.ctxArrProp = [0, 1, 2];
}
throwError() { throw 'boom'; }
throwError() {
throw 'boom';
}
}
@Component({
@ -2326,7 +2354,9 @@ class ChildCompNoTemplate {
@Component({selector: 'child-cmp-svc', template: '{{ctxProp}}'})
class ChildCompUsingService {
ctxProp: string;
constructor(service: MyService) { this.ctxProp = service.greeting; }
constructor(service: MyService) {
this.ctxProp = service.greeting;
}
}
@Directive({selector: 'some-directive'})
@ -2341,7 +2371,9 @@ class SomeDirectiveMissingAnnotation {}
})
class CompWithHost {
myHost: SomeDirective;
constructor(@Host() someComp: SomeDirective) { this.myHost = someComp; }
constructor(@Host() someComp: SomeDirective) {
this.myHost = someComp;
}
}
@Component({selector: '[child-cmp2]', viewProviders: [MyService]})
@ -2384,7 +2416,9 @@ class NoContext {
@Pipe({name: 'double'})
class DoublePipe implements PipeTransform, OnDestroy {
ngOnDestroy() {}
transform(value: any) { return `${value}${value}`; }
transform(value: any) {
return `${value}${value}`;
}
}
@Directive({selector: '[emitter]', outputs: ['event']})
@ -2397,7 +2431,9 @@ class DirectiveEmittingEvent {
this.event = new EventEmitter();
}
fireEvent(msg: string) { this.event.emit(msg); }
fireEvent(msg: string) {
this.event.emit(msg);
}
}
@Directive({selector: '[update-host-attributes]', host: {'role': 'button'}})
@ -2408,16 +2444,22 @@ class DirectiveUpdatingHostAttributes {
class DirectiveUpdatingHostProperties {
id: string;
constructor() { this.id = 'one'; }
constructor() {
this.id = 'one';
}
}
@Directive({selector: '[listener]', host: {'(event)': 'onEvent($event)'}})
class DirectiveListeningEvent {
msg: string;
constructor() { this.msg = ''; }
constructor() {
this.msg = '';
}
onEvent(msg: string) { this.msg = msg; }
onEvent(msg: string) {
this.msg = msg;
}
}
@Directive({
@ -2431,17 +2473,27 @@ class DirectiveListeningEvent {
})
class DirectiveListeningDomEvent {
eventTypes: string[] = [];
onEvent(eventType: string) { this.eventTypes.push(eventType); }
onWindowEvent(eventType: string) { this.eventTypes.push('window_' + eventType); }
onDocumentEvent(eventType: string) { this.eventTypes.push('document_' + eventType); }
onBodyEvent(eventType: string) { this.eventTypes.push('body_' + eventType); }
onEvent(eventType: string) {
this.eventTypes.push(eventType);
}
onWindowEvent(eventType: string) {
this.eventTypes.push('window_' + eventType);
}
onDocumentEvent(eventType: string) {
this.eventTypes.push('document_' + eventType);
}
onBodyEvent(eventType: string) {
this.eventTypes.push('body_' + eventType);
}
}
let globalCounter = 0;
@Directive({selector: '[listenerother]', host: {'(window:domEvent)': 'onEvent($event.type)'}})
class DirectiveListeningDomEventOther {
eventType: string;
constructor() { this.eventType = ''; }
constructor() {
this.eventType = '';
}
onEvent(eventType: string) {
globalCounter++;
this.eventType = 'other_' + eventType;
@ -2450,18 +2502,22 @@ class DirectiveListeningDomEventOther {
@Directive({selector: '[listenerprevent]', host: {'(click)': 'onEvent($event)'}})
class DirectiveListeningDomEventPrevent {
onEvent(event: any) { return false; }
onEvent(event: any) {
return false;
}
}
@Directive({selector: '[listenernoprevent]', host: {'(click)': 'onEvent($event)'}})
class DirectiveListeningDomEventNoPrevent {
onEvent(event: any) { return true; }
onEvent(event: any) {
return true;
}
}
@Directive({selector: '[id]', inputs: ['id']})
class IdDir {
// TODO(issue/24571): remove '!'.
id !: string;
id!: string;
}
@Directive({selector: '[customEvent]'})
@ -2497,7 +2553,9 @@ class PrivateImpl extends PublicApi {
@Directive({selector: '[needs-public-api]'})
class NeedsPublicApi {
constructor(@Host() api: PublicApi) { expect(api instanceof PrivateImpl).toBe(true); }
constructor(@Host() api: PublicApi) {
expect(api instanceof PrivateImpl).toBe(true);
}
}
class ToolbarContext {
@ -2507,7 +2565,9 @@ class ToolbarContext {
@Directive({selector: '[toolbarpart]'})
class ToolbarPart {
templateRef: TemplateRef<ToolbarContext>;
constructor(templateRef: TemplateRef<ToolbarContext>) { this.templateRef = templateRef; }
constructor(templateRef: TemplateRef<ToolbarContext>) {
this.templateRef = templateRef;
}
}
@Directive({selector: '[toolbarVc]', inputs: ['toolbarVc']})
@ -2525,7 +2585,7 @@ class ToolbarViewContainer {
})
class ToolbarComponent {
// TODO(issue/24571): remove '!'.
@ContentChildren(ToolbarPart) query !: QueryList<ToolbarPart>;
@ContentChildren(ToolbarPart) query!: QueryList<ToolbarPart>;
ctxProp: string = 'hello world';
constructor() {}
@ -2536,7 +2596,9 @@ class DirectiveWithTwoWayBinding {
controlChange = new EventEmitter();
control: any = null;
triggerChange(value: any) { this.controlChange.emit(value); }
triggerChange(value: any) {
this.controlChange.emit(value);
}
}
@Injectable()
@ -2585,7 +2647,9 @@ class DirectiveProvidingInjectableInHostAndView {
class DirectiveConsumingInjectable {
injectable: any;
constructor(@Host() @Inject(InjectableService) injectable: any) { this.injectable = injectable; }
constructor(@Host() @Inject(InjectableService) injectable: any) {
this.injectable = injectable;
}
}
@ -2620,12 +2684,14 @@ class EventBus {
@Directive({
selector: 'grand-parent-providing-event-bus',
providers: [{provide: EventBus, useValue: new EventBus(null !, 'grandparent')}]
providers: [{provide: EventBus, useValue: new EventBus(null!, 'grandparent')}]
})
class GrandParentProvidingEventBus {
bus: EventBus;
constructor(bus: EventBus) { this.bus = bus; }
constructor(bus: EventBus) {
this.bus = bus;
}
}
function createParentBus(peb: EventBus) {
@ -2651,7 +2717,9 @@ class ParentProvidingEventBus {
class ChildConsumingEventBus {
bus: EventBus;
constructor(@SkipSelf() bus: EventBus) { this.bus = bus; }
constructor(@SkipSelf() bus: EventBus) {
this.bus = bus;
}
}
@Directive({selector: '[someImpvp]', inputs: ['someImpvp']})
@ -2695,17 +2763,23 @@ class ComponentWithoutView {
@Directive({selector: '[no-duplicate]'})
class DuplicateDir {
constructor(elRef: ElementRef) { elRef.nativeElement.textContent += 'noduplicate'; }
constructor(elRef: ElementRef) {
elRef.nativeElement.textContent += 'noduplicate';
}
}
@Directive({selector: '[no-duplicate]'})
class OtherDuplicateDir {
constructor(elRef: ElementRef) { elRef.nativeElement.textContent += 'othernoduplicate'; }
constructor(elRef: ElementRef) {
elRef.nativeElement.textContent += 'othernoduplicate';
}
}
@Directive({selector: 'directive-throwing-error'})
class DirectiveThrowingAnError {
constructor() { throw new Error('BOOM'); }
constructor() {
throw new Error('BOOM');
}
}
@Component({
@ -2721,15 +2795,19 @@ class DirectiveWithPropDecorators {
target: any;
// TODO(issue/24571): remove '!'.
@Input('elProp') dirProp !: string;
@Input('elProp') dirProp!: string;
@Output('elEvent') event = new EventEmitter();
// TODO(issue/24571): remove '!'.
@HostBinding('attr.my-attr') myAttr !: string;
@HostBinding('attr.my-attr') myAttr!: string;
@HostListener('click', ['$event.target'])
onClick(target: any) { this.target = target; }
onClick(target: any) {
this.target = target;
}
fireEvent(msg: any) { this.event.emit(msg); }
fireEvent(msg: any) {
this.event.emit(msg);
}
}
@Component({selector: 'some-cmp'})

View File

@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler';
import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
import {Component, Directive, Injectable, NgModule, OnDestroy, Pipe} from '@angular/core';
import {TestBed, async, getTestBed} from '@angular/core/testing';
import {async, getTestBed, TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {obsoleteInIvy} from '@angular/private/testing';
@ -32,7 +32,7 @@ import {obsoleteInIvy} from '@angular/private/testing';
}
function expectInstanceCreated(type: any) {
const instance = instances.get(type) !;
const instance = instances.get(type)!;
expect(instance).toBeDefined();
expect(instance.dep instanceof SomeDep).toBe(true);
}
@ -46,7 +46,9 @@ import {obsoleteInIvy} from '@angular/private/testing';
class SomeDirective extends Base {}
class SomePipe extends Base {
transform(value: any) { return value; }
transform(value: any) {
return value;
}
}
class SomeService extends Base {}
@ -138,7 +140,9 @@ import {obsoleteInIvy} from '@angular/private/testing';
createSummaries().then(s => summaries = s);
}));
afterEach(() => { resetTestEnvironmentWithSummaries(); });
afterEach(() => {
resetTestEnvironmentWithSummaries();
});
it('should use directive metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries);

View File

@ -15,15 +15,20 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy} from '@angular/private/testing';
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
function declareTests(config?: {useJit: boolean}) {
describe('<ng-container>', function() {
beforeEach(() => {
TestBed.configureCompiler({...config});
TestBed.configureTestingModule({
@ -140,7 +145,7 @@ function declareTests(config?: {useJit: boolean}) {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const q = fixture.debugElement.children[0].references !['q'];
const q = fixture.debugElement.children[0].references!['q'];
fixture.detectChanges();
expect(q.textDirChildren.length).toEqual(1);
@ -153,7 +158,7 @@ function declareTests(config?: {useJit: boolean}) {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const q = fixture.debugElement.children[0].references !['q'];
const q = fixture.debugElement.children[0].references!['q'];
fixture.detectChanges();
expect(q.textDirChildren.length).toEqual(1);
@ -170,21 +175,25 @@ class TextDirective {
@Component({selector: 'needs-content-children', template: ''})
class NeedsContentChildren implements AfterContentInit {
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective) textDirChildren !: QueryList<TextDirective>;
@ContentChildren(TextDirective) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
numberOfChildrenAfterContentInit !: number;
numberOfChildrenAfterContentInit!: number;
ngAfterContentInit() { this.numberOfChildrenAfterContentInit = this.textDirChildren.length; }
ngAfterContentInit() {
this.numberOfChildrenAfterContentInit = this.textDirChildren.length;
}
}
@Component({selector: 'needs-view-children', template: '<div text></div>'})
class NeedsViewChildren implements AfterViewInit {
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) textDirChildren !: QueryList<TextDirective>;
@ViewChildren(TextDirective) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
numberOfChildrenAfterViewInit !: number;
numberOfChildrenAfterViewInit!: number;
ngAfterViewInit() { this.numberOfChildrenAfterViewInit = this.textDirChildren.length; }
ngAfterViewInit() {
this.numberOfChildrenAfterViewInit = this.textDirChildren.length;
}
}
@Component({selector: 'simple', template: 'SIMPLE(<ng-content></ng-content>)'})

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled, ɵɵdefineNgModule as defineNgModule} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, forwardRef, getModuleFactory, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, ɵivyEnabled as ivyEnabled, ɵɵdefineNgModule as defineNgModule} from '@angular/core';
import {Console} from '@angular/core/src/console';
import {ɵɵInjectableDef, ɵɵdefineInjectable} from '@angular/core/src/di/interface/defs';
import {ɵɵdefineInjectable, ɵɵInjectableDef} from '@angular/core/src/di/interface/defs';
import {getNgModuleDef} from '@angular/core/src/render3/definition';
import {NgModuleData} from '@angular/core/src/view/types';
import {tokenKey} from '@angular/core/src/view/util';
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@ -23,7 +23,9 @@ import {stringify} from '../../src/util/stringify';
class Engine {}
class BrokenEngine {
constructor() { throw new Error('Broken Engine'); }
constructor() {
throw new Error('Broken Engine');
}
}
class DashboardSoftware {}
@ -53,7 +55,9 @@ class CarWithDashboard {
@Injectable()
class SportsCar extends Car {
constructor(engine: Engine) { super(engine); }
constructor(engine: Engine) {
super(engine);
}
}
@Injectable()
@ -79,13 +83,14 @@ class SomeComp {
@Directive({selector: '[someDir]'})
class SomeDirective {
// TODO(issue/24571): remove '!'.
@HostBinding('title') @Input()
someDir !: string;
@HostBinding('title') @Input() someDir!: string;
}
@Pipe({name: 'somePipe'})
class SomePipe {
transform(value: string): any { return `transformed ${value}`; }
transform(value: string): any {
return `transformed ${value}`;
}
}
@Component({selector: 'comp', template: `<div [someDir]="'someValue' | somePipe"></div>`})
@ -94,10 +99,16 @@ class CompUsingModuleDirectiveAndPipe {
{
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
}
@ -106,7 +117,9 @@ function declareTests(config?: {useJit: boolean}) {
let compiler: Compiler;
let injector: Injector;
beforeEach(() => { TestBed.configureCompiler(config || {}); });
beforeEach(() => {
TestBed.configureCompiler(config || {});
});
beforeEach(inject([Compiler, Injector], (_compiler: Compiler, _injector: Injector) => {
compiler = _compiler;
@ -117,8 +130,7 @@ function declareTests(config?: {useJit: boolean}) {
return compiler.compileModuleSync(moduleType);
}
function createModule<T>(
moduleType: Type<T>, parentInjector?: Injector | null): NgModuleRef<T> {
function createModule<T>(moduleType: Type<T>, parentInjector?: Injector|null): NgModuleRef<T> {
// Read the `ngModuleDef` to cause it to be compiled and any errors thrown.
getNgModuleDef(moduleType);
return createModuleFactory(moduleType).create(parentInjector || null);
@ -136,11 +148,11 @@ function declareTests(config?: {useJit: boolean}) {
const ngModule = createModule(moduleType, injector);
const cf = ngModule.componentFactoryResolver.resolveComponentFactory(compType) !;
const cf = ngModule.componentFactoryResolver.resolveComponentFactory(compType)!;
const comp = cf.create(Injector.NULL);
return new ComponentFixture(comp, null !, false);
return new ComponentFixture(comp, null!, false);
}
describe('errors', () => {
@ -150,8 +162,8 @@ function declareTests(config?: {useJit: boolean}) {
}
expect(() => createModule(SomeModule))
.toThrowError(
`Can't export directive ${stringify(SomeDirective)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
.toThrowError(`Can't export directive ${stringify(SomeDirective)} from ${
stringify(SomeModule)} as it was neither declared nor imported!`);
});
it('should error when exporting a pipe that was neither declared nor imported', () => {
@ -160,8 +172,8 @@ function declareTests(config?: {useJit: boolean}) {
}
expect(() => createModule(SomeModule))
.toThrowError(
`Can't export pipe ${stringify(SomePipe)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
.toThrowError(`Can't export pipe ${stringify(SomePipe)} from ${
stringify(SomeModule)} as it was neither declared nor imported!`);
});
it('should error if a directive is declared in more than 1 module', () => {
@ -177,9 +189,14 @@ function declareTests(config?: {useJit: boolean}) {
expect(() => createModule(Module2))
.toThrowError(
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${
stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${
stringify(SomeDirective)} to a higher module that imports ${
stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${
stringify(SomeDirective)} then import that NgModule in ${
stringify(Module1)} and ${stringify(Module2)}.`);
});
it('should error if a directive is declared in more than 1 module also if the module declaring it is imported',
@ -194,9 +211,14 @@ function declareTests(config?: {useJit: boolean}) {
expect(() => createModule(Module2))
.toThrowError(
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${
stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${
stringify(SomeDirective)} to a higher module that imports ${
stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${
stringify(SomeDirective)} then import that NgModule in ${
stringify(Module1)} and ${stringify(Module2)}.`);
});
it('should error if a pipe is declared in more than 1 module', () => {
@ -212,9 +234,13 @@ function declareTests(config?: {useJit: boolean}) {
expect(() => createModule(Module2))
.toThrowError(
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${
stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${
stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${
stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${
stringify(Module2)}.`);
});
it('should error if a pipe is declared in more than 1 module also if the module declaring it is imported',
@ -229,11 +255,14 @@ function declareTests(config?: {useJit: boolean}) {
expect(() => createModule(Module2))
.toThrowError(
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${
stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${
stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${
stringify(SomePipe)} then import that NgModule in ${
stringify(Module1)} and ${stringify(Module2)}.`);
});
});
describe('schemas', () => {
@ -361,7 +390,7 @@ function declareTests(config?: {useJit: boolean}) {
}
const ngModule = createModule(SomeModule);
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp) !.componentType)
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp)!.componentType)
.toBe(SomeComp);
expect(ngModule.injector.get(ComponentFactoryResolver)
.resolveComponentFactory(SomeComp)
@ -411,7 +440,7 @@ function declareTests(config?: {useJit: boolean}) {
}
const ngModule = createModule(SomeModule);
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp) !.componentType)
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp)!.componentType)
.toBe(SomeComp);
expect(ngModule.injector.get(ComponentFactoryResolver)
.resolveComponentFactory(SomeComp)
@ -429,7 +458,7 @@ function declareTests(config?: {useJit: boolean}) {
}
const ngModule = createModule(SomeModule);
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp) !.componentType)
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp)!.componentType)
.toBe(SomeComp);
expect(ngModule.injector.get(ComponentFactoryResolver)
.resolveComponentFactory(SomeComp)
@ -447,14 +476,13 @@ function declareTests(config?: {useJit: boolean}) {
}
const ngModule = createModule(SomeModule);
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp) !.componentType)
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp)!.componentType)
.toBe(SomeComp);
expect(ngModule.injector.get(ComponentFactoryResolver)
.resolveComponentFactory(SomeComp)
.componentType)
.toBe(SomeComp);
});
});
describe('bootstrap components', () => {
@ -464,7 +492,7 @@ function declareTests(config?: {useJit: boolean}) {
}
const ngModule = createModule(SomeModule);
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp) !.componentType)
expect(ngModule.componentFactoryResolver.resolveComponentFactory(SomeComp)!.componentType)
.toBe(SomeComp);
});
@ -477,7 +505,6 @@ function declareTests(config?: {useJit: boolean}) {
expect(ngModule._bootstrapComponents.length).toBe(1);
expect(ngModule._bootstrapComponents[0]).toBe(SomeComp);
});
});
describe('directives and pipes', () => {
@ -542,7 +569,6 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('import/export', () => {
it('should support exported directives and pipes', () => {
@NgModule({declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeImportedModule {
@ -677,7 +703,7 @@ function declareTests(config?: {useJit: boolean}) {
let moduleType: any = null;
function createInjector(providers: Provider[], parent?: Injector | null): Injector {
function createInjector(providers: Provider[], parent?: Injector|null): Injector {
@NgModule({providers: providers})
class SomeModule {
}
@ -687,8 +713,9 @@ function declareTests(config?: {useJit: boolean}) {
return createModule(SomeModule, parent).injector;
}
it('should provide the module',
() => { expect(createInjector([]).get(moduleType)).toBeAnInstanceOf(moduleType); });
it('should provide the module', () => {
expect(createInjector([]).get(moduleType)).toBeAnInstanceOf(moduleType);
});
it('should instantiate a class without dependencies', () => {
const injector = createInjector([Engine]);
@ -741,7 +768,9 @@ function declareTests(config?: {useJit: boolean}) {
});
it('should provide to a factory', () => {
function sportsCarFactory(e: Engine) { return new SportsCar(e); }
function sportsCarFactory(e: Engine) {
return new SportsCar(e);
}
const injector =
createInjector([Engine, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
@ -759,8 +788,7 @@ function declareTests(config?: {useJit: boolean}) {
it('should provide to an alias', () => {
const injector = createInjector([
Engine, {provide: SportsCar, useClass: SportsCar},
{provide: Car, useExisting: SportsCar}
Engine, {provide: SportsCar, useClass: SportsCar}, {provide: Car, useExisting: SportsCar}
]);
const car = injector.get(Car);
@ -879,7 +907,6 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('injecting lazy providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@NgModule({
providers: [
@ -920,7 +947,6 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('injecting eager providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@NgModule({
providers: [
@ -1039,7 +1065,6 @@ function declareTests(config?: {useJit: boolean}) {
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
});
describe('depedency resolution', () => {
@ -1075,7 +1100,9 @@ function declareTests(config?: {useJit: boolean}) {
@NgModule()
class ImportedModule {
constructor() { created = true; }
constructor() {
created = true;
}
}
@NgModule({imports: [ImportedModule]})
@ -1105,7 +1132,9 @@ function declareTests(config?: {useJit: boolean}) {
let destroyed = false;
class SomeInjectable {
ngOnDestroy() { destroyed = true; }
ngOnDestroy() {
destroyed = true;
}
}
@NgModule({providers: [SomeInjectable]})
@ -1125,8 +1154,12 @@ function declareTests(config?: {useJit: boolean}) {
let destroyed = false;
class SomeInjectable {
constructor() { created = true; }
ngOnDestroy() { destroyed = true; }
constructor() {
created = true;
}
ngOnDestroy() {
destroyed = true;
}
}
@NgModule({providers: [SomeInjectable]})
@ -1174,9 +1207,8 @@ function declareTests(config?: {useJit: boolean}) {
}
@NgModule({
imports: [
{ngModule: ImportedModule, providers: [{provide: 'token1', useValue: 'imported'}]}
]
imports:
[{ngModule: ImportedModule, providers: [{provide: 'token1', useValue: 'imported'}]}]
})
class SomeModule {
}
@ -1210,9 +1242,8 @@ function declareTests(config?: {useJit: boolean}) {
@NgModule({
providers: [{provide: 'token1', useValue: 'direct'}],
imports: [
{ngModule: ImportedModule, providers: [{provide: 'token1', useValue: 'imported'}]}
]
imports:
[{ngModule: ImportedModule, providers: [{provide: 'token1', useValue: 'imported'}]}]
})
class SomeModule {
}

View File

@ -7,7 +7,7 @@
*/
import {CommonModule, ɵgetDOM as getDOM} from '@angular/common';
import {Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, Injector, Input, NO_ERRORS_SCHEMA, NgModule, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, Injector, Input, NgModule, NO_ERRORS_SCHEMA, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -50,9 +50,7 @@ describe('projection', () => {
it('should support projecting text interpolation to a non bound element with other bound elements after it',
() => {
TestBed.overrideComponent(Simple, {
set: {
template: 'SIMPLE(<div><ng-content></ng-content></div><div [tabIndex]="0">EL</div>)'
}
set: {template: 'SIMPLE(<div><ng-content></ng-content></div><div [tabIndex]="0">EL</div>)'}
});
TestBed.overrideComponent(MainComp, {set: {template: '<simple>{{text}}</simple>'}});
const main = TestBed.createComponent(MainComp);
@ -244,8 +242,7 @@ describe('projection', () => {
it('should support nesting with content being direct child of a nested component', () => {
TestBed.configureTestingModule({
declarations:
[InnerComponent, InnerInnerComponent, OuterComponent, ManualViewportDirective]
declarations: [InnerComponent, InnerInnerComponent, OuterComponent, ManualViewportDirective]
});
TestBed.overrideComponent(MainComp, {
set: {
@ -304,7 +301,7 @@ describe('projection', () => {
`<ng-content></ng-content>(<ng-template [ngIf]="showing"><ng-content select="div"></ng-content></ng-template>)`
})
class Child {
@Input() showing !: boolean;
@Input() showing!: boolean;
}
@Component({
@ -361,11 +358,13 @@ describe('projection', () => {
});
it('should support moving non projected light dom around', () => {
let sourceDirective: ManualViewportDirective = undefined !;
let sourceDirective: ManualViewportDirective = undefined!;
@Directive({selector: '[manual]'})
class ManualViewportDirective {
constructor(public templateRef: TemplateRef<Object>) { sourceDirective = this; }
constructor(public templateRef: TemplateRef<Object>) {
sourceDirective = this;
}
}
TestBed.configureTestingModule(
@ -594,7 +593,6 @@ describe('projection', () => {
it('should project nodes into nested templates when the main template doesn\'t have <ng-content>',
() => {
@Component({
selector: 'content-in-template',
template:
@ -622,7 +620,6 @@ describe('projection', () => {
});
it('should project nodes into nested templates and the main template', () => {
@Component({
selector: 'content-in-main-and-template',
template:
@ -720,7 +717,6 @@ describe('projection', () => {
});
describe('projectable nodes', () => {
@Component({selector: 'test', template: ''})
class TestComponent {
constructor(public cfr: ComponentFactoryResolver) {}
@ -739,14 +735,20 @@ describe('projection', () => {
class InsertTplRef implements OnInit {
constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<{}>) {}
ngOnInit() { this._vcRef.createEmbeddedView(this._tplRef); }
ngOnInit() {
this._vcRef.createEmbeddedView(this._tplRef);
}
}
@Directive({selector: '[delayedInsert]', exportAs: 'delayedInsert'})
class DelayedInsertTplRef {
constructor(public vc: ViewContainerRef, public templateRef: TemplateRef<Object>) {}
show() { this.vc.createEmbeddedView(this.templateRef); }
hide() { this.vc.clear(); }
show() {
this.vc.createEmbeddedView(this.templateRef);
}
hide() {
this.vc.clear();
}
}
@NgModule({
@ -893,15 +895,23 @@ class SingleContentTagComponent {
@Directive({selector: '[manual]'})
class ManualViewportDirective {
constructor(public vc: ViewContainerRef, public templateRef: TemplateRef<Object>) {}
show() { this.vc.createEmbeddedView(this.templateRef); }
hide() { this.vc.clear(); }
show() {
this.vc.createEmbeddedView(this.templateRef);
}
hide() {
this.vc.clear();
}
}
@Directive({selector: '[project]'})
class ProjectDirective {
constructor(public vc: ViewContainerRef) {}
show(templateRef: TemplateRef<Object>) { this.vc.createEmbeddedView(templateRef); }
hide() { this.vc.clear(); }
show(templateRef: TemplateRef<Object>) {
this.vc.createEmbeddedView(templateRef);
}
hide() {
this.vc.clear();
}
}
@Component({
@ -1034,5 +1044,5 @@ class CmpA2 {
}
function supportsNativeShadowDOM(): boolean {
return typeof(<any>document.body).createShadowRoot === 'function';
return typeof (<any>document.body).createShadowRoot === 'function';
}

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, asNativeElements, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {ElementRef} from '@angular/core/src/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
import {Subject} from 'rxjs';
@ -16,7 +16,6 @@ import {Subject} from 'rxjs';
import {stringify} from '../../src/util/stringify';
describe('Query API', () => {
beforeEach(() => TestBed.configureTestingModule({
declarations: [
MyComp0,
@ -76,7 +75,7 @@ describe('Query API', () => {
const template = '<needs-content-children #q><div text="foo"></div></needs-content-children>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
view.detectChanges();
expect(q.textDirChildren.length).toEqual(1);
expect(q.numberOfChildrenAfterContentInit).toEqual(1);
@ -88,7 +87,7 @@ describe('Query API', () => {
const view = createTestCmp(MyComp0, template);
view.componentInstance.shouldShow = true;
view.detectChanges();
const q: NeedsContentChild = view.debugElement.children[0].references !['q'];
const q: NeedsContentChild = view.debugElement.children[0].references!['q'];
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
view.componentInstance.shouldShow = false;
@ -114,7 +113,7 @@ describe('Query API', () => {
`;
const view = createTestCmp(MyComp0, template);
view.detectChanges();
const q: NeedsContentChild = view.debugElement.children[1].references !['q'];
const q: NeedsContentChild = view.debugElement.children[1].references!['q'];
expect(q.child.text).toEqual('foo');
const directive: DirectiveNeedsContentChild =
view.debugElement.children[0].injector.get(DirectiveNeedsContentChild);
@ -125,7 +124,7 @@ describe('Query API', () => {
const template = '<needs-view-child #q></needs-view-child>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
const q: NeedsViewChild = view.debugElement.children[0].references!['q'];
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
q.shouldShow = false;
@ -140,7 +139,7 @@ describe('Query API', () => {
const template =
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
const view = createTestCmp(MyComp0, template);
const q: NeedsStaticContentAndViewChild = view.debugElement.children[0].references !['q'];
const q: NeedsStaticContentAndViewChild = view.debugElement.children[0].references!['q'];
expect(q.contentChild.text).toBeFalsy();
expect(q.viewChild.text).toBeFalsy();
@ -161,7 +160,7 @@ describe('Query API', () => {
const view = TestBed.createComponent(MyComp0);
view.detectChanges();
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
const q: NeedsViewChild = view.debugElement.children[0].references!['q'];
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
q.shouldShow = false;
@ -252,8 +251,8 @@ describe('Query API', () => {
const template = '<has-null-query-condition></has-null-query-condition>';
TestBed.overrideComponent(MyCompBroken0, {set: {template}});
expect(() => TestBed.createComponent(MyCompBroken0))
.toThrowError(
`Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
.toThrowError(`Can't construct a query for the property "errorTrigger" of "${
stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
});
});
@ -422,7 +421,7 @@ describe('Query API', () => {
'</needs-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
q.query.changes.subscribe({
next: () => {
@ -444,11 +443,13 @@ describe('Query API', () => {
let isQueryListCompleted = false;
const q: NeedsQuery = view.debugElement.children[0].references !['q'];
const q: NeedsQuery = view.debugElement.children[0].references!['q'];
const changes = <Subject<any>>q.query.changes;
expect(q.query.length).toEqual(1);
expect(changes.closed).toBeFalsy();
changes.subscribe(() => {}, () => {}, () => { isQueryListCompleted = true; });
changes.subscribe(() => {}, () => {}, () => {
isQueryListCompleted = true;
});
view.componentInstance.shouldShow = false;
view.detectChanges();
@ -457,7 +458,7 @@ describe('Query API', () => {
view.componentInstance.shouldShow = true;
view.detectChanges();
const q2: NeedsQuery = view.debugElement.children[0].references !['q'];
const q2: NeedsQuery = view.debugElement.children[0].references!['q'];
expect(q2.query.length).toEqual(1);
expect(changes.closed).toBeTruthy();
@ -472,7 +473,7 @@ describe('Query API', () => {
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-ref-binding>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
view.componentInstance.list = ['1d', '2d'];
view.detectChanges();
@ -486,7 +487,7 @@ describe('Query API', () => {
'<div text="two" #textLabel2="textDir"></div>' +
'</needs-query-by-ref-bindings>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
expect(q.query.first.text).toEqual('one');
expect(q.query.last.text).toEqual('two');
@ -497,7 +498,7 @@ describe('Query API', () => {
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'</needs-query-by-ref-binding>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
view.componentInstance.list = ['1d', '2d'];
view.detectChanges();
@ -513,7 +514,7 @@ describe('Query API', () => {
'</div>' +
'</needs-query-by-ref-binding>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
view.componentInstance.list = ['1d', '2d'];
view.detectChanges();
@ -534,14 +535,14 @@ describe('Query API', () => {
const template = '<needs-view-query-by-ref-binding #q></needs-view-query-by-ref-binding>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryByLabel = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryByLabel = view.debugElement.children[0].references!['q'];
expect(q.query.first.nativeElement).toHaveText('text');
});
it('should contain all child directives in the view dom', () => {
const template = '<needs-view-children #q></needs-view-children>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
expect(q.textDirChildren.length).toEqual(1);
expect(q.numberOfChildrenAfterViewInit).toEqual(1);
});
@ -551,21 +552,21 @@ describe('Query API', () => {
it('should contain all the elements in the view with that have the given directive', () => {
const template = '<needs-view-query #q><div text="ignoreme"></div></needs-view-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQuery = view.debugElement.children[0].references !['q'];
const q: NeedsViewQuery = view.debugElement.children[0].references!['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
});
it('should not include directive present on the host element', () => {
const template = '<needs-view-query #q text="self"></needs-view-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQuery = view.debugElement.children[0].references !['q'];
const q: NeedsViewQuery = view.debugElement.children[0].references!['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
});
it('should reflect changes in the component', () => {
const template = '<needs-view-query-if #q></needs-view-query-if>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryIf = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryIf = view.debugElement.children[0].references!['q'];
expect(q.query.length).toBe(0);
q.show = true;
@ -577,7 +578,7 @@ describe('Query API', () => {
it('should not be affected by other changes in the component', () => {
const template = '<needs-view-query-nested-if #q></needs-view-query-nested-if>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryNestedIf = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryNestedIf = view.debugElement.children[0].references!['q'];
expect(q.query.length).toEqual(1);
expect(q.query.first.text).toEqual('1');
@ -592,7 +593,7 @@ describe('Query API', () => {
() => {
const template = '<needs-view-query-order #q></needs-view-query-order>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrder = view.debugElement.children[0].references!['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
@ -605,7 +606,7 @@ describe('Query API', () => {
() => {
const template = '<needs-view-query-order-with-p #q></needs-view-query-order-with-p>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryOrderWithParent = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrderWithParent = view.debugElement.children[0].references!['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
q.list = ['-3', '2'];
@ -616,7 +617,7 @@ describe('Query API', () => {
it('should handle long ngFor cycles', () => {
const template = '<needs-view-query-order #q></needs-view-query-order>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrder = view.debugElement.children[0].references!['q'];
// no significance to 50, just a reasonably large cycle.
for (let i = 0; i < 50; i++) {
@ -630,7 +631,7 @@ describe('Query API', () => {
it('should support more than three queries', () => {
const template = '<needs-four-queries #q><div text="1"></div></needs-four-queries>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
expect(q.query1).toBeDefined();
expect(q.query2).toBeDefined();
expect(q.query3).toBeDefined();
@ -643,7 +644,7 @@ describe('Query API', () => {
const template =
'<manual-projecting #q><ng-template><div text="1"></div></ng-template></manual-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
expect(q.query.length).toBe(0);
q.create();
@ -665,7 +666,7 @@ describe('Query API', () => {
</ng-template>
</manual-projecting>`;
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
const q = view.debugElement.children[0].references!['q'] as ManualProjecting;
expect(q.query.length).toBe(0);
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
@ -694,7 +695,7 @@ describe('Query API', () => {
</ng-template>
</manual-projecting>`;
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
const q = view.debugElement.children[0].references!['q'] as ManualProjecting;
expect(q.query.length).toBe(0);
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
@ -729,7 +730,7 @@ describe('Query API', () => {
</div>
`;
const view = createTestCmp(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
view.componentInstance.shouldShow = true;
view.detectChanges();
@ -751,13 +752,11 @@ describe('Query API', () => {
class AutoProjecting {
// TODO(issue/24571):
// remove '!'.
@ContentChild(TemplateRef)
content !: TemplateRef<any>;
@ContentChild(TemplateRef) content!: TemplateRef<any>;
// TODO(issue/24571):
// remove '!'.
@ContentChildren(TextDirective)
query !: QueryList<TextDirective>;
@ContentChildren(TextDirective) query!: QueryList<TextDirective>;
}
TestBed.configureTestingModule({declarations: [AutoProjecting]});
@ -765,7 +764,7 @@ describe('Query API', () => {
'<auto-projecting #q><ng-template><div text="1"></div></ng-template></auto-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
// This should be 1, but due to
// https://github.com/angular/angular/issues/15117
// this is 0.
@ -783,13 +782,11 @@ describe('Query API', () => {
class AutoProjecting {
// TODO(issue/24571):
// remove '!'.
@ContentChild(TemplateRef)
content !: TemplateRef<any>;
@ContentChild(TemplateRef) content!: TemplateRef<any>;
// TODO(issue/24571):
// remove '!'.
@ContentChildren(TextDirective)
query !: QueryList<TextDirective>;
@ContentChildren(TextDirective) query!: QueryList<TextDirective>;
}
TestBed.configureTestingModule({declarations: [AutoProjecting]});
@ -797,7 +794,7 @@ describe('Query API', () => {
'<auto-projecting #q><ng-template><div text="1"></div></ng-template></auto-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references!['q'];
expect(q.query.length).toBe(1);
});
}
@ -808,35 +805,39 @@ describe('Query API', () => {
@Directive({selector: '[text]', inputs: ['text'], exportAs: 'textDir'})
class TextDirective {
// TODO(issue/24571): remove '!'.
text !: string;
text!: string;
constructor() {}
}
@Component({selector: 'needs-content-children', template: ''})
class NeedsContentChildren implements AfterContentInit {
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective) textDirChildren !: QueryList<TextDirective>;
@ContentChildren(TextDirective) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
numberOfChildrenAfterContentInit !: number;
numberOfChildrenAfterContentInit!: number;
ngAfterContentInit() { this.numberOfChildrenAfterContentInit = this.textDirChildren.length; }
ngAfterContentInit() {
this.numberOfChildrenAfterContentInit = this.textDirChildren.length;
}
}
@Component({selector: 'needs-view-children', template: '<div text></div>'})
class NeedsViewChildren implements AfterViewInit {
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) textDirChildren !: QueryList<TextDirective>;
@ViewChildren(TextDirective) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
numberOfChildrenAfterViewInit !: number;
numberOfChildrenAfterViewInit!: number;
ngAfterViewInit() { this.numberOfChildrenAfterViewInit = this.textDirChildren.length; }
ngAfterViewInit() {
this.numberOfChildrenAfterViewInit = this.textDirChildren.length;
}
}
@Component({selector: 'needs-content-child', template: ''})
class NeedsContentChild implements AfterContentInit, AfterContentChecked {
/** @internal */
// TODO(issue/24571): remove '!'.
_child !: TextDirective;
_child!: TextDirective;
@ContentChild(TextDirective)
set child(value) {
@ -844,18 +845,24 @@ class NeedsContentChild implements AfterContentInit, AfterContentChecked {
this.logs.push(['setter', value ? value.text : null]);
}
get child() { return this._child; }
get child() {
return this._child;
}
logs: any[] /** TODO #9100 */ = [];
ngAfterContentInit() { this.logs.push(['init', this.child ? this.child.text : null]); }
ngAfterContentInit() {
this.logs.push(['init', this.child ? this.child.text : null]);
}
ngAfterContentChecked() { this.logs.push(['check', this.child ? this.child.text : null]); }
ngAfterContentChecked() {
this.logs.push(['check', this.child ? this.child.text : null]);
}
}
@Directive({selector: '[directive-needs-content-child]'})
class DirectiveNeedsContentChild {
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective) child !: TextDirective;
@ContentChild(TextDirective) child!: TextDirective;
}
@Component({selector: 'needs-view-child', template: `<div *ngIf="shouldShow" text="foo"></div>`})
@ -864,7 +871,7 @@ class NeedsViewChild implements AfterViewInit, AfterViewChecked {
shouldShow2: boolean = false;
/** @internal */
// TODO(issue/24571): remove '!'.
_child !: TextDirective;
_child!: TextDirective;
@ViewChild(TextDirective)
set child(value) {
@ -872,12 +879,18 @@ class NeedsViewChild implements AfterViewInit, AfterViewChecked {
this.logs.push(['setter', value ? value.text : null]);
}
get child() { return this._child; }
get child() {
return this._child;
}
logs: any[] /** TODO #9100 */ = [];
ngAfterViewInit() { this.logs.push(['init', this.child ? this.child.text : null]); }
ngAfterViewInit() {
this.logs.push(['init', this.child ? this.child.text : null]);
}
ngAfterViewChecked() { this.logs.push(['check', this.child ? this.child.text : null]); }
ngAfterViewChecked() {
this.logs.push(['check', this.child ? this.child.text : null]);
}
}
function createTestCmp<T>(type: Type<T>, template: string): ComponentFixture<T> {
@ -895,9 +908,9 @@ function createTestCmpAndDetectChanges<T>(type: Type<T>, template: string): Comp
@Component({selector: 'needs-static-content-view-child', template: `<div text="viewFoo"></div>`})
class NeedsStaticContentAndViewChild {
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective, {static: true}) contentChild !: TextDirective;
@ContentChild(TextDirective, {static: true}) contentChild!: TextDirective;
// TODO(issue/24571): remove '!'.
@ViewChild(TextDirective, {static: true}) viewChild !: TextDirective;
@ViewChild(TextDirective, {static: true}) viewChild!: TextDirective;
}
@Directive({selector: '[dir]'})
@ -910,19 +923,19 @@ class InertDirective {
})
class NeedsQuery {
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective) query !: QueryList<TextDirective>;
@ContentChildren(TextDirective) query!: QueryList<TextDirective>;
}
@Component({selector: 'needs-four-queries', template: ''})
class NeedsFourQueries {
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective) query1 !: TextDirective;
@ContentChild(TextDirective) query1!: TextDirective;
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective) query2 !: TextDirective;
@ContentChild(TextDirective) query2!: TextDirective;
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective) query3 !: TextDirective;
@ContentChild(TextDirective) query3!: TextDirective;
// TODO(issue/24571): remove '!'.
@ContentChild(TextDirective) query4 !: TextDirective;
@ContentChild(TextDirective) query4!: TextDirective;
}
@Component({
@ -931,25 +944,25 @@ class NeedsFourQueries {
})
class NeedsQueryDesc {
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective, {descendants: true}) query !: QueryList<TextDirective>;
@ContentChildren(TextDirective, {descendants: true}) query!: QueryList<TextDirective>;
}
@Component({selector: 'needs-query-by-ref-binding', template: '<ng-content>'})
class NeedsQueryByLabel {
// TODO(issue/24571): remove '!'.
@ContentChildren('textLabel', {descendants: true}) query !: QueryList<any>;
@ContentChildren('textLabel', {descendants: true}) query!: QueryList<any>;
}
@Component({selector: 'needs-view-query-by-ref-binding', template: '<div #textLabel>text</div>'})
class NeedsViewQueryByLabel {
// TODO(issue/24571): remove '!'.
@ViewChildren('textLabel') query !: QueryList<any>;
@ViewChildren('textLabel') query!: QueryList<any>;
}
@Component({selector: 'needs-query-by-ref-bindings', template: '<ng-content>'})
class NeedsQueryByTwoLabels {
// TODO(issue/24571): remove '!'.
@ContentChildren('textLabel1,textLabel2', {descendants: true}) query !: QueryList<any>;
@ContentChildren('textLabel1,textLabel2', {descendants: true}) query!: QueryList<any>;
}
@Component({
@ -958,7 +971,7 @@ class NeedsQueryByTwoLabels {
})
class NeedsQueryAndProject {
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective) query !: QueryList<TextDirective>;
@ContentChildren(TextDirective) query!: QueryList<TextDirective>;
}
@Component({
@ -967,14 +980,14 @@ class NeedsQueryAndProject {
})
class NeedsViewQuery {
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) query !: QueryList<TextDirective>;
@ViewChildren(TextDirective) query!: QueryList<TextDirective>;
}
@Component({selector: 'needs-view-query-if', template: '<div *ngIf="show" text="1"></div>'})
class NeedsViewQueryIf {
show: boolean = false;
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) query !: QueryList<TextDirective>;
@ViewChildren(TextDirective) query!: QueryList<TextDirective>;
}
@Component({
@ -984,7 +997,7 @@ class NeedsViewQueryIf {
class NeedsViewQueryNestedIf {
show: boolean = true;
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) query !: QueryList<TextDirective>;
@ViewChildren(TextDirective) query!: QueryList<TextDirective>;
}
@Component({
@ -995,7 +1008,7 @@ class NeedsViewQueryNestedIf {
})
class NeedsViewQueryOrder {
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) query !: QueryList<TextDirective>;
@ViewChildren(TextDirective) query!: QueryList<TextDirective>;
list: string[] = ['2', '3'];
}
@ -1007,16 +1020,16 @@ class NeedsViewQueryOrder {
})
class NeedsViewQueryOrderWithParent {
// TODO(issue/24571): remove '!'.
@ViewChildren(TextDirective) query !: QueryList<TextDirective>;
@ViewChildren(TextDirective) query!: QueryList<TextDirective>;
list: string[] = ['2', '3'];
}
@Component({selector: 'needs-tpl', template: '<ng-template><div>shadow</div></ng-template>'})
class NeedsTpl {
// TODO(issue/24571): remove '!'.
@ViewChildren(TemplateRef) viewQuery !: QueryList<TemplateRef<Object>>;
@ViewChildren(TemplateRef) viewQuery!: QueryList<TemplateRef<Object>>;
// TODO(issue/24571): remove '!'.
@ContentChildren(TemplateRef) query !: QueryList<TemplateRef<Object>>;
@ContentChildren(TemplateRef) query!: QueryList<TemplateRef<Object>>;
constructor(public vc: ViewContainerRef) {}
}
@ -1024,33 +1037,31 @@ class NeedsTpl {
{selector: 'needs-named-tpl', template: '<ng-template #tpl><div>shadow</div></ng-template>'})
class NeedsNamedTpl {
// TODO(issue/24571): remove '!'.
@ViewChild('tpl', {static: true}) viewTpl !: TemplateRef<Object>;
@ViewChild('tpl', {static: true}) viewTpl!: TemplateRef<Object>;
// TODO(issue/24571): remove '!'.
@ContentChild('tpl', {static: true}) contentTpl !: TemplateRef<Object>;
@ContentChild('tpl', {static: true}) contentTpl!: TemplateRef<Object>;
constructor(public vc: ViewContainerRef) {}
}
@Component({selector: 'needs-content-children-read', template: ''})
class NeedsContentChildrenWithRead {
// TODO(issue/24571): remove '!'.
@ContentChildren('q', {read: TextDirective}) textDirChildren !: QueryList<TextDirective>;
@ContentChildren('q', {read: TextDirective}) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
@ContentChildren('nonExisting', {read: TextDirective}) nonExistingVar !: QueryList<TextDirective>;
@ContentChildren('nonExisting', {read: TextDirective}) nonExistingVar!: QueryList<TextDirective>;
}
@Component({selector: 'needs-content-child-read', template: ''})
class NeedsContentChildWithRead {
// TODO(issue/24571): remove '!'.
@ContentChild('q', {read: TextDirective}) textDirChild !: TextDirective;
@ContentChild('q', {read: TextDirective}) textDirChild!: TextDirective;
// TODO(issue/24571): remove '!'.
@ContentChild('nonExisting', {read: TextDirective})
nonExistingVar !: TextDirective;
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar!: TextDirective;
}
@Component({selector: 'needs-content-children-shallow', template: ''})
class NeedsContentChildrenShallow {
@ContentChildren('q', {descendants: false})
children !: QueryList<ElementRef>;
@ContentChildren('q', {descendants: false}) children!: QueryList<ElementRef>;
}
@Component({
@ -1059,7 +1070,7 @@ class NeedsContentChildrenShallow {
})
class NeedsContentChildTemplateRef {
// TODO(issue/24571): remove '!'.
@ContentChild(TemplateRef, {static: true}) templateRef !: TemplateRef<any>;
@ContentChild(TemplateRef, {static: true}) templateRef!: TemplateRef<any>;
}
@Component({
@ -1077,9 +1088,9 @@ class NeedsContentChildTemplateRefApp {
})
class NeedsViewChildrenWithRead {
// TODO(issue/24571): remove '!'.
@ViewChildren('q,w', {read: TextDirective}) textDirChildren !: QueryList<TextDirective>;
@ViewChildren('q,w', {read: TextDirective}) textDirChildren!: QueryList<TextDirective>;
// TODO(issue/24571): remove '!'.
@ViewChildren('nonExisting', {read: TextDirective}) nonExistingVar !: QueryList<TextDirective>;
@ViewChildren('nonExisting', {read: TextDirective}) nonExistingVar!: QueryList<TextDirective>;
}
@Component({
@ -1088,27 +1099,28 @@ class NeedsViewChildrenWithRead {
})
class NeedsViewChildWithRead {
// TODO(issue/24571): remove '!'.
@ViewChild('q', {read: TextDirective}) textDirChild !: TextDirective;
@ViewChild('q', {read: TextDirective}) textDirChild!: TextDirective;
// TODO(issue/24571): remove '!'.
@ViewChild('nonExisting', {read: TextDirective}) nonExistingVar !: TextDirective;
@ViewChild('nonExisting', {read: TextDirective}) nonExistingVar!: TextDirective;
}
@Component({selector: 'needs-viewcontainer-read', template: '<div #q></div>'})
class NeedsViewContainerWithRead {
// TODO(issue/24571): remove '!'.
@ViewChild('q', {read: ViewContainerRef}) vc !: ViewContainerRef;
@ViewChild('q', {read: ViewContainerRef}) vc!: ViewContainerRef;
// TODO(issue/24571): remove '!'.
@ViewChild('nonExisting', {read: ViewContainerRef})
nonExistingVar !: ViewContainerRef;
@ViewChild('nonExisting', {read: ViewContainerRef}) nonExistingVar!: ViewContainerRef;
// TODO(issue/24571): remove '!'.
@ContentChild(TemplateRef, {static: true}) template !: TemplateRef<Object>;
createView() { this.vc.createEmbeddedView(this.template); }
createView() {
this.vc.createEmbeddedView(this.template);
}
}
@Component({selector: 'has-null-query-condition', template: '<div></div>'})
class HasNullQueryCondition {
@ContentChildren(null !) errorTrigger: any;
@ContentChildren(null!) errorTrigger: any;
}
@Component({selector: 'my-comp', template: ''})
@ -1127,14 +1139,16 @@ class ManualProjecting {
@ContentChild(TemplateRef, {static: true}) template !: TemplateRef<any>;
// TODO(issue/24571): remove '!'.
@ViewChild('vc', {read: ViewContainerRef})
vc !: ViewContainerRef;
@ViewChild('vc', {read: ViewContainerRef}) vc!: ViewContainerRef;
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective)
query !: QueryList<TextDirective>;
@ContentChildren(TextDirective) query!: QueryList<TextDirective>;
create() { this.vc.createEmbeddedView(this.template); }
create() {
this.vc.createEmbeddedView(this.template);
}
destroy() { this.vc.clear(); }
destroy() {
this.vc.clear();
}
}

View File

@ -21,10 +21,11 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
log = '';
});
function logAppend(item: any /** TODO #9100 */) { log += (log.length == 0 ? '' : ', ') + item; }
function logAppend(item: any /** TODO #9100 */) {
log += (log.length == 0 ? '' : ', ') + item;
}
describe('dirty and reset', () => {
it('should initially be dirty and empty', () => {
expect(queryList.dirty).toBeTruthy();
expect(queryList.length).toBe(0);
@ -36,7 +37,6 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
expect(queryList.dirty).toBeFalsy();
expect(queryList.length).toBe(2);
});
});
it('should support resetting and iterating over the new objects', () => {
@ -146,7 +146,7 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
// For loops use the iteration protocol.
for (const value of queryListAsIterable) {
expect(value).toBe(data.shift() !);
expect(value).toBe(data.shift()!);
}
expect(data.length).toBe(0);
});
@ -155,7 +155,11 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
describe('simple observable interface', () => {
it('should fire callbacks on change', fakeAsync(() => {
let fires = 0;
queryList.changes.subscribe({next: (_) => { fires += 1; }});
queryList.changes.subscribe({
next: (_) => {
fires += 1;
}
});
queryList.notifyOnChanges();
tick();
@ -170,7 +174,11 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
it('should provides query list as an argument', fakeAsync(() => {
let recorded: any /** TODO #9100 */;
queryList.changes.subscribe({next: (v: any) => { recorded = v; }});
queryList.changes.subscribe({
next: (v: any) => {
recorded = v;
}
});
queryList.reset(['one']);
queryList.notifyOnChanges();

View File

@ -7,18 +7,24 @@
*/
import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, destroyPlatform, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, destroyPlatform, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {BrowserModule, By} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
declareTestsUsingBootstrap();
@ -26,11 +32,14 @@ declareTestsUsingBootstrap();
function declareTests(config?: {useJit: boolean}) {
// Place to put reproductions for regressions
describe('regressions', () => {
beforeEach(() => { TestBed.configureTestingModule({declarations: [MyComp1, PlatformPipe]}); });
beforeEach(() => {
TestBed.configureTestingModule({declarations: [MyComp1, PlatformPipe]});
});
describe('platform pipes', () => {
beforeEach(() => { TestBed.configureCompiler({...config}); });
beforeEach(() => {
TestBed.configureCompiler({...config});
});
it('should overwrite them by custom pipes', () => {
TestBed.configureTestingModule({declarations: [CustomPipe]});
@ -84,14 +93,20 @@ function declareTests(config?: {useJit: boolean}) {
class MyDir {
setterCalls: {[key: string]: any} = {};
// TODO(issue/24571): remove '!'.
changes !: SimpleChanges;
changes!: SimpleChanges;
@Input()
set a(v: number) { this.setterCalls['a'] = v; }
set a(v: number) {
this.setterCalls['a'] = v;
}
@Input()
set b(v: number) { this.setterCalls['b'] = v; }
set b(v: number) {
this.setterCalls['b'] = v;
}
ngOnChanges(changes: SimpleChanges) { this.changes = changes; }
ngOnChanges(changes: SimpleChanges) {
this.changes = changes;
}
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
@ -128,7 +143,7 @@ function declareTests(config?: {useJit: boolean}) {
@Component({selector: 'some-comp', template: '<p (click)="nullValue?.click()"></p>'})
class SomeComponent {
// TODO(issue/24571): remove '!'.
nullValue !: SomeReferencedClass;
nullValue!: SomeReferencedClass;
}
class SomeReferencedClass {
@ -195,20 +210,17 @@ function declareTests(config?: {useJit: boolean}) {
});
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}
]))
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', () => {
@ -298,8 +310,7 @@ function declareTests(config?: {useJit: boolean}) {
@Component({template: '<div #vc></div><div *ngIf="show" #vc></div>'})
class MyComp {
// TODO(issue/24571): remove '!'.
@ViewChildren('vc', {read: ViewContainerRef})
viewContainers !: QueryList<ViewContainerRef>;
@ViewChildren('vc', {read: ViewContainerRef}) viewContainers!: QueryList<ViewContainerRef>;
show = true;
}
@ -361,7 +372,7 @@ function declareTests(config?: {useJit: boolean}) {
@Directive({selector: 'test'})
class Test {
// TODO(issue/24571): remove '!'.
@Input() @ContentChild(TemplateRef, {static: true}) tpl !: TemplateRef<any>;
@Input() @ContentChild(TemplateRef, {static: true}) tpl!: TemplateRef<any>;
}
@Component({
@ -391,7 +402,7 @@ function declareTests(config?: {useJit: boolean}) {
.it('should throw if @ContentChild and @Input are on the same property', () => {
@Directive({selector: 'test'})
class Test {
@Input() @ContentChild(TemplateRef, {static: true}) tpl !: TemplateRef<any>;
@Input() @ContentChild(TemplateRef, {static: true}) tpl!: TemplateRef<any>;
}
@Component({selector: 'my-app', template: `<test></test>`})
@ -412,8 +423,8 @@ function declareTests(config?: {useJit: boolean}) {
class MyModule {
}
const modRef = TestBed.configureTestingModule({imports: [MyModule]})
.get(NgModuleRef) as NgModuleRef<MyModule>;
const modRef = TestBed.configureTestingModule({imports: [MyModule]}).get(NgModuleRef) as
NgModuleRef<MyModule>;
const compRef =
modRef.componentFactoryResolver.resolveComponentFactory(App).create(Injector.NULL);
@ -429,7 +440,9 @@ function declareTestsUsingBootstrap() {
class MockConsole {
errors: any[][] = [];
error(...s: any[]): void { this.errors.push(s); }
error(...s: any[]): void {
this.errors.push(s);
}
}
let logger: MockConsole;
@ -445,7 +458,9 @@ function declareTestsUsingBootstrap() {
(errorHandler as any)._console = logger as any;
}));
afterEach(() => { destroyPlatform(); });
afterEach(() => {
destroyPlatform();
});
if (getDOM().supportsDOMEvents()) {
// This test needs a real DOM....
@ -459,7 +474,9 @@ function declareTestsUsingBootstrap() {
class ErrorComp {
value = 0;
thrownValue = 0;
next() { this.value++; }
next() {
this.value++;
}
nextAndThrow() {
this.value++;
this.throwIfNeeded();
@ -475,11 +492,12 @@ function declareTestsUsingBootstrap() {
@Directive({selector: '[dirClick]'})
class EventDir {
@Output()
dirClick = new EventEmitter();
@Output() dirClick = new EventEmitter();
@HostListener('click', ['$event'])
onClick(event: any) { this.dirClick.next(event); }
onClick(event: any) {
this.dirClick.next(event);
}
}
@NgModule({
@ -552,12 +570,16 @@ class MyComp1 {
@Pipe({name: 'somePipe', pure: true})
class PlatformPipe implements PipeTransform {
transform(value: any): any { return 'somePlatformPipe'; }
transform(value: any): any {
return 'somePlatformPipe';
}
}
@Pipe({name: 'somePipe', pure: true})
class CustomPipe implements PipeTransform {
transform(value: any): any { return 'someCustomPipe'; }
transform(value: any): any {
return 'someCustomPipe';
}
}
@Component({selector: 'cmp-content', template: `<ng-content></ng-content>`})
@ -571,7 +593,9 @@ class MyCountingComp {
return {value: 'counting method value'};
}
static reset() { MyCountingComp.calls = 0; }
static reset() {
MyCountingComp.calls = 0;
}
static calls = 0;
}
@ -581,7 +605,9 @@ class CountingPipe implements PipeTransform {
CountingPipe.calls++;
return {value: 'counting pipe value'};
}
static reset() { CountingPipe.calls = 0; }
static reset() {
CountingPipe.calls = 0;
}
static calls = 0;
}

View File

@ -8,16 +8,22 @@
import {ɵgetDOM as getDOM} from '@angular/common';
import {Component, Directive, HostBinding, Input, NO_ERRORS_SCHEMA, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {ComponentFixture, TestBed, getTestBed} from '@angular/core/testing';
import {ComponentFixture, getTestBed, TestBed} from '@angular/core/testing';
import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
{
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
describe('ivy', () => {
declareTests();
});
} else {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
describe('jit', () => {
declareTests({useJit: true});
});
describe('no jit', () => {
declareTests({useJit: false});
});
}
}
@ -34,7 +40,6 @@ class OnPrefixDir {
function declareTests(config?: {useJit: boolean}) {
describe('security integration tests', function() {
beforeEach(() => {
TestBed.configureCompiler({...config}).configureTestingModule({
declarations: [
@ -49,7 +54,9 @@ function declareTests(config?: {useJit: boolean}) {
originalLog = getDOM().log;
getDOM().log = (msg) => { /* disable logging */ };
});
afterEach(() => { getDOM().log = originalLog; });
afterEach(() => {
getDOM().log = originalLog;
});
describe('events', () => {
modifiedInIvy('on-prefixed attributes validation happens at runtime in Ivy')
@ -113,7 +120,7 @@ function declareTests(config?: {useJit: boolean}) {
});
// should not throw for inputs starting with "on"
let cmp: ComponentFixture<SecuredComponent> = undefined !;
let cmp: ComponentFixture<SecuredComponent> = undefined!;
expect(() => cmp = TestBed.createComponent(SecuredComponent)).not.toThrow();
// must bind to the directive not to the property of the div
@ -124,7 +131,6 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getProperty(div.nativeElement, 'onclick')).not.toBe(value);
expect(div.nativeElement.hasAttribute('onclick')).toEqual(false);
});
});
describe('safe HTML values', function() {
@ -206,8 +212,7 @@ function declareTests(config?: {useJit: boolean}) {
@Directive({selector: '[dirHref]'})
class HrefDirective {
// TODO(issue/24571): remove '!'.
@HostBinding('href') @Input()
dirHref !: string;
@HostBinding('href') @Input() dirHref!: string;
}
const template = `<a [dirHref]="ctxProp">Link Title</a>`;
@ -222,8 +227,7 @@ function declareTests(config?: {useJit: boolean}) {
@Directive({selector: '[dirHref]'})
class HrefDirective {
// TODO(issue/24571): remove '!'.
@HostBinding('attr.href') @Input()
dirHref !: string;
@HostBinding('attr.href') @Input() dirHref!: string;
}
const template = `<a [dirHref]="ctxProp">Link Title</a>`;

View File

@ -16,7 +16,7 @@ import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/
import {CompilerFacade, ExportedCompilerFacade} from '@angular/core/src/compiler/compiler_facade';
import {getErrorLogger} from '@angular/core/src/errors';
import {resolveComponentResources} from '@angular/core/src/metadata/resource_loading';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
describe('jit source mapping', () => {
@ -44,7 +44,9 @@ describe('jit source mapping', () => {
.describe('(View Engine)', () => {
describe('inline templates', () => {
const ngUrl = 'ng:///DynamicTestModule/MyComp.html';
function templateDecorator(template: string) { return {template}; }
function templateDecorator(template: string) {
return {template};
}
declareTests({ngUrl, templateDecorator});
});
@ -66,7 +68,9 @@ describe('jit source mapping', () => {
class MyComp {
}
expect(() => { compileAndCreateComponent(MyComp); })
expect(() => {
compileAndCreateComponent(MyComp);
})
.toThrowError(
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:2`));
}));
@ -76,7 +80,9 @@ describe('jit source mapping', () => {
class MyComp {
}
expect(() => { compileAndCreateComponent(MyComp); })
expect(() => {
compileAndCreateComponent(MyComp);
})
.toThrowError(
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
}));
@ -105,7 +111,9 @@ describe('jit source mapping', () => {
@Directive({selector: '[someDir]'})
class SomeDir {
constructor() { throw new Error('Test'); }
constructor() {
throw new Error('Test');
}
}
TestBed.configureTestingModule({declarations: [SomeDir]});
@ -163,7 +171,9 @@ describe('jit source mapping', () => {
@Component({...templateDecorator(template)})
class MyComp {
createError() { throw new Error('Test'); }
createError() {
throw new Error('Test');
}
}
const comp = compileAndCreateComponent(MyComp);
@ -195,7 +205,9 @@ describe('jit source mapping', () => {
@Component({...templateDecorator(template)})
class MyComp {
createError() { throw new Error('Test'); }
createError() {
throw new Error('Test');
}
}
const comp = compileAndCreateComponent(MyComp);
@ -219,19 +231,19 @@ describe('jit source mapping', () => {
column: 4,
source: ngUrl,
});
}));
}
});
onlyInIvy('Generated filenames and stack traces have changed in ivy').describe('(Ivy)', () => {
beforeEach(() => overrideCompilerFacade());
afterEach(() => restoreCompilerFacade());
describe('inline templates', () => {
const ngUrl = 'ng:///MyComp/template.html';
function templateDecorator(template: string) { return {template}; }
function templateDecorator(template: string) {
return {template};
}
declareTests({ngUrl, templateDecorator});
});
@ -268,7 +280,9 @@ describe('jit source mapping', () => {
class MyComp {
}
expect(() => { resolveCompileAndCreateComponent(MyComp, template); })
expect(() => {
resolveCompileAndCreateComponent(MyComp, template);
})
.toThrowError(
new RegExp(`Template parse errors[\\s\\S]*${escapeRegExp(ngUrl)}@1:7`));
}));
@ -297,7 +311,9 @@ describe('jit source mapping', () => {
@Directive({selector: '[someDir]'})
class SomeDir {
constructor() { throw new Error('Test'); }
constructor() {
throw new Error('Test');
}
}
TestBed.configureTestingModule({declarations: [SomeDir]});
@ -351,7 +367,9 @@ describe('jit source mapping', () => {
@Component({...templateDecorator(template)})
class MyComp {
createError() { throw new Error('Test'); }
createError() {
throw new Error('Test');
}
}
const comp = resolveCompileAndCreateComponent(MyComp, template);
@ -375,7 +393,9 @@ describe('jit source mapping', () => {
@Component({...templateDecorator(template)})
class MyComp {
createError() { throw new Error('Test'); }
createError() {
throw new Error('Test');
}
}
const comp = resolveCompileAndCreateComponent(MyComp, template);
@ -414,7 +434,9 @@ describe('jit source mapping', () => {
return TestBed.createComponent(comType);
}
function createResolver(contents: string) { return (_url: string) => Promise.resolve(contents); }
function createResolver(contents: string) {
return (_url: string) => Promise.resolve(contents);
}
function resolveCompileAndCreateComponent(comType: any, template: string) {
resolveComponentResources(createResolver(template));
@ -438,7 +460,9 @@ describe('jit source mapping', () => {
interface TestConfig {
ngUrl: string;
templateDecorator: (template: string) => { [key: string]: any };
templateDecorator: (template: string) => {
[key: string]: any
};
}
interface SourcePos {
@ -448,8 +472,8 @@ describe('jit source mapping', () => {
}
/**
* A helper class that captures the sources that have been JIT compiled.
*/
* A helper class that captures the sources that have been JIT compiled.
*/
class MockJitEvaluator extends JitEvaluator {
sources: string[] = [];
@ -466,7 +490,7 @@ describe('jit source mapping', () => {
*/
getSourceMap(genFile: string): SourceMap {
return this.sources.map(source => extractSourceMap(source))
.find(map => !!(map && map.file === genFile)) !;
.find(map => !!(map && map.file === genFile))!;
}
getSourcePositionForStack(stack: string, genFile: string): SourcePos {
@ -475,9 +499,9 @@ describe('jit source mapping', () => {
.map(line => urlRegexp.exec(line))
.filter(match => !!match)
.map(match => ({
file: match ![1],
line: parseInt(match ![2], 10),
column: parseInt(match ![3], 10)
file: match![1],
line: parseInt(match![2], 10),
column: parseInt(match![3], 10)
}))
.shift();
if (!pos) {
@ -489,8 +513,8 @@ describe('jit source mapping', () => {
}
function getErrorLoggerStack(e: Error): string {
let logStack: string = undefined !;
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack !}, e.message);
let logStack: string = undefined!;
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack!}, e.message);
return logStack;
}
});

View File

@ -32,12 +32,15 @@ describe('SystemJsNgModuleLoader', () => {
'prefixed/test/suffixed': {'NamedNgFactory': 'test module factory'}
});
});
afterEach(() => { global['System'] = oldSystem; });
afterEach(() => {
global['System'] = oldSystem;
});
it('loads a default factory by appending the factory suffix', async(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test').then(
contents => { expect(contents).toBe('test module factory' as any); });
loader.load('test').then(contents => {
expect(contents).toBe('test module factory' as any);
});
}));
it('loads a named factory by appending the factory suffix', async(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
@ -63,12 +66,15 @@ describe('SystemJsNgModuleLoader', () => {
'test': {'default': 'test module', 'NamedModule': 'test NamedModule'},
});
});
afterEach(() => { global['System'] = oldSystem; });
afterEach(() => {
global['System'] = oldSystem;
});
it('loads a default module', async(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test').then(
contents => { expect(contents.moduleType).toBe('test module' as any); });
loader.load('test').then(contents => {
expect(contents.moduleType).toBe('test module' as any);
});
}));
it('loads a named module', async(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());

View File

@ -7,7 +7,7 @@
*/
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, DebugElement, Directive, ElementRef, EmbeddedViewRef, Host, Inject, InjectionToken, Injector, Input, NgModule, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@ -32,61 +32,81 @@ class CycleDirective {
@Directive({selector: '[needsDirectiveFromSelf]'})
class NeedsDirectiveFromSelf {
dependency: SimpleDirective;
constructor(@Self() dependency: SimpleDirective) { this.dependency = dependency; }
constructor(@Self() dependency: SimpleDirective) {
this.dependency = dependency;
}
}
@Directive({selector: '[optionallyNeedsDirective]'})
class OptionallyNeedsDirective {
dependency: SimpleDirective;
constructor(@Self() @Optional() dependency: SimpleDirective) { this.dependency = dependency; }
constructor(@Self() @Optional() dependency: SimpleDirective) {
this.dependency = dependency;
}
}
@Directive({selector: '[needsComponentFromHost]'})
class NeedsComponentFromHost {
dependency: SimpleComponent;
constructor(@Host() dependency: SimpleComponent) { this.dependency = dependency; }
constructor(@Host() dependency: SimpleComponent) {
this.dependency = dependency;
}
}
@Directive({selector: '[needsDirectiveFromHost]'})
class NeedsDirectiveFromHost {
dependency: SimpleDirective;
constructor(@Host() dependency: SimpleDirective) { this.dependency = dependency; }
constructor(@Host() dependency: SimpleDirective) {
this.dependency = dependency;
}
}
@Directive({selector: '[needsDirective]'})
class NeedsDirective {
dependency: SimpleDirective;
constructor(dependency: SimpleDirective) { this.dependency = dependency; }
constructor(dependency: SimpleDirective) {
this.dependency = dependency;
}
}
@Directive({selector: '[needsService]'})
class NeedsService {
service: any;
constructor(@Inject('service') service: any) { this.service = service; }
constructor(@Inject('service') service: any) {
this.service = service;
}
}
@Directive({selector: '[needsAppService]'})
class NeedsAppService {
service: any;
constructor(@Inject('appService') service: any) { this.service = service; }
constructor(@Inject('appService') service: any) {
this.service = service;
}
}
@Component({selector: '[needsHostAppService]', template: ''})
class NeedsHostAppService {
service: any;
constructor(@Host() @Inject('appService') service: any) { this.service = service; }
constructor(@Host() @Inject('appService') service: any) {
this.service = service;
}
}
@Component({selector: '[needsServiceComponent]', template: ''})
class NeedsServiceComponent {
service: any;
constructor(@Inject('service') service: any) { this.service = service; }
constructor(@Inject('service') service: any) {
this.service = service;
}
}
@Directive({selector: '[needsServiceFromHost]'})
class NeedsServiceFromHost {
service: any;
constructor(@Host() @Inject('service') service: any) { this.service = service; }
constructor(@Host() @Inject('service') service: any) {
this.service = service;
}
}
@Directive({selector: '[needsAttribute]'})
@ -146,36 +166,50 @@ class PushComponentNeedsChangeDetectorRef {
@Pipe({name: 'purePipe', pure: true})
class PurePipe implements PipeTransform {
constructor() {}
transform(value: any): any { return this; }
transform(value: any): any {
return this;
}
}
@Pipe({name: 'impurePipe', pure: false})
class ImpurePipe implements PipeTransform {
constructor() {}
transform(value: any): any { return this; }
transform(value: any): any {
return this;
}
}
@Pipe({name: 'pipeNeedsChangeDetectorRef'})
class PipeNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
transform(value: any): any { return this; }
transform(value: any): any {
return this;
}
}
@Pipe({name: 'pipeNeedsService'})
export class PipeNeedsService implements PipeTransform {
service: any;
constructor(@Inject('service') service: any) { this.service = service; }
transform(value: any): any { return this; }
constructor(@Inject('service') service: any) {
this.service = service;
}
transform(value: any): any {
return this;
}
}
@Pipe({name: 'duplicatePipe'})
export class DuplicatePipe1 implements PipeTransform {
transform(value: any): any { return this; }
transform(value: any): any {
return this;
}
}
@Pipe({name: 'duplicatePipe'})
export class DuplicatePipe2 implements PipeTransform {
transform(value: any): any { return this; }
transform(value: any): any {
return this;
}
}
@Component({selector: 'root', template: ''})
@ -183,15 +217,15 @@ class TestComp {
}
function createComponentFixture<T>(
template: string, providers?: Provider[] | null, comp?: Type<T>): ComponentFixture<T> {
template: string, providers?: Provider[]|null, comp?: Type<T>): ComponentFixture<T> {
if (!comp) {
comp = <any>TestComp;
}
TestBed.overrideComponent(comp !, {set: {template}});
TestBed.overrideComponent(comp!, {set: {template}});
if (providers && providers.length) {
TestBed.overrideComponent(comp !, {add: {providers: providers}});
TestBed.overrideComponent(comp!, {add: {providers: providers}});
}
return TestBed.createComponent(comp !);
return TestBed.createComponent(comp!);
}
function createComponent(template: string, providers?: Provider[], comp?: Type<any>): DebugElement {
@ -351,7 +385,6 @@ describe('View injector', () => {
});
describe('injecting lazy providers into an eager provider via Injector.get', () => {
it('should inject providers that were declared before it', () => {
@Component({
template: '',
@ -448,7 +481,9 @@ describe('View injector', () => {
@Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''})
class SomeComponent {
public a: string;
constructor(injector: Injector) { this.a = injector.get('a'); }
constructor(injector: Injector) {
this.a = injector.get('a');
}
}
const comp = TestBed.configureTestingModule({declarations: [SomeComponent]})
@ -461,8 +496,12 @@ describe('View injector', () => {
let destroyed = false;
class SomeInjectable {
constructor() { created = true; }
ngOnDestroy() { destroyed = true; }
constructor() {
created = true;
}
ngOnDestroy() {
destroyed = true;
}
}
@Component({providers: [SomeInjectable], template: ''})
@ -910,7 +949,7 @@ describe('View injector', () => {
const testInjector = <Injector>{
get: (token: any, notFoundValue: any) =>
token === 'someToken' ? 'someNewValue' : notFoundValue
token === 'someToken' ? 'someNewValue' : notFoundValue
};
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
@ -961,8 +1000,7 @@ describe('View injector', () => {
it('should inject ChangeDetectorRef into pipes', () => {
TestBed.configureTestingModule({
declarations:
[SimpleDirective, PipeNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
declarations: [SimpleDirective, PipeNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
});
const el = createComponent(
'<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>');

View File

@ -68,7 +68,6 @@ import {TestBed} from '@angular/core/testing';
view.detectChanges();
expect(view.componentInstance.children).toBeDefined();
expect(view.componentInstance.children.length).toBe(2);
});
});
}
@ -77,30 +76,30 @@ import {TestBed} from '@angular/core/testing';
@Directive({selector: 'simple'})
class Simple {
// TODO(issue/24571): remove '!'.
@Input() marker !: string;
@Input() marker!: string;
}
@Component({selector: 'view-child-type-selector', template: ''})
class ViewChildTypeSelectorComponent {
// TODO(issue/24571): remove '!'.
@ViewChild(Simple) child !: Simple;
@ViewChild(Simple) child!: Simple;
}
@Component({selector: 'view-child-string-selector', template: ''})
class ViewChildStringSelectorComponent {
// TODO(issue/24571): remove '!'.
@ViewChild('child') child !: ElementRef;
@ViewChild('child') child!: ElementRef;
}
@Component({selector: 'view-children-type-selector', template: ''})
class ViewChildrenTypeSelectorComponent {
// TODO(issue/24571): remove '!'.
@ViewChildren(Simple) children !: QueryList<Simple>;
@ViewChildren(Simple) children!: QueryList<Simple>;
}
@Component({selector: 'view-child-string-selector', template: ''})
class ViewChildrenStringSelectorComponent {
// Allow comma separated selector (with spaces).
// TODO(issue/24571): remove '!'.
@ViewChildren('child1 , child2') children !: QueryList<ElementRef>;
@ViewChildren('child1 , child2') children!: QueryList<ElementRef>;
}

View File

@ -16,7 +16,7 @@ describe('resource_loading', () => {
describe('error handling', () => {
it('should throw an error when compiling component that has unresolved templateUrl', () => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
compileComponent(MyComponent, {templateUrl: 'someUrl'});
expect(() => MyComponent.ɵcmp).toThrowError(`
Component 'MyComponent' is not resolved:
@ -25,7 +25,7 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
});
it('should throw an error when compiling component that has unresolved styleUrls', () => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
compileComponent(MyComponent, {styleUrls: ['someUrl1', 'someUrl2']});
expect(() => MyComponent.ɵcmp).toThrowError(`
Component 'MyComponent' is not resolved:
@ -35,7 +35,7 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
it('should throw an error when compiling component that has unresolved templateUrl and styleUrls',
() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
compileComponent(
MyComponent, {templateUrl: 'someUrl', styleUrls: ['someUrl1', 'someUrl2']});
expect(() => MyComponent.ɵcmp).toThrowError(`
@ -59,8 +59,8 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
}
beforeEach(() => resourceFetchCount = 0);
it('should resolve template', async() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
it('should resolve template', async () => {
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const metadata: Component = {templateUrl: 'test://content'};
compileComponent(MyComponent, metadata);
await resolveComponentResources(testResolver);
@ -69,8 +69,8 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
expect(resourceFetchCount).toBe(1);
});
it('should resolve styleUrls', async() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
it('should resolve styleUrls', async () => {
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style2']};
compileComponent(MyComponent, metadata);
await resolveComponentResources(testResolver);
@ -80,8 +80,8 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
expect(resourceFetchCount).toBe(2);
});
it('should cache multiple resolution to same URL', async() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
it('should cache multiple resolution to same URL', async () => {
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style1']};
compileComponent(MyComponent, metadata);
await resolveComponentResources(testResolver);
@ -91,8 +91,8 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
expect(resourceFetchCount).toBe(1);
});
it('should keep order even if the resolution is out of order', async() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
it('should keep order even if the resolution is out of order', async () => {
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const metadata: Component = {
template: '',
styles: ['existing'],
@ -113,8 +113,8 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
});
it('should not add components without external resources to resolution queue', () => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
const MyComponent2: ComponentType<any> = (class MyComponent{}) as any;
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const MyComponent2: ComponentType<any> = (class MyComponent {}) as any;
compileComponent(MyComponent, {template: ''});
expect(isComponentResourceResolutionQueueEmpty()).toBe(true);
@ -127,12 +127,14 @@ Did you run and wait for 'resolveComponentResources()'?`.trim());
describe('fetch', () => {
function fetch(url: string): Promise<Response> {
return Promise.resolve({
text() { return 'response for ' + url; }
text() {
return 'response for ' + url;
}
} as any as Response);
}
it('should work with fetch', async() => {
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
it('should work with fetch', async () => {
const MyComponent: ComponentType<any> = (class MyComponent {}) as any;
const metadata: Component = {templateUrl: 'test://content'};
compileComponent(MyComponent, metadata);
await resolveComponentResources(fetch);

View File

@ -7,13 +7,13 @@
*/
import {Reflector} from '@angular/core/src/reflection/reflection';
import {ReflectionCapabilities, isDelegateCtor} from '@angular/core/src/reflection/reflection_capabilities';
import {isDelegateCtor, ReflectionCapabilities} from '@angular/core/src/reflection/reflection_capabilities';
import {makeDecorator, makeParamDecorator, makePropDecorator} from '@angular/core/src/util/decorators';
import {global} from '@angular/core/src/util/global';
interface ClassDecoratorFactory {
(data: ClassDecorator): any;
new (data: ClassDecorator): ClassDecorator;
new(data: ClassDecorator): ClassDecorator;
}
interface ClassDecorator {
@ -46,10 +46,12 @@ class ClassWithDecorators {
b: AType;
@PropDecorator('p3')
set c(value: any) {}
set c(value: any) {
}
@PropDecorator('p4')
someMethod() {}
someMethod() {
}
constructor(@ParamDecorator('a') a: AType, @ParamDecorator('b') b: AType) {
this.a = a;
@ -64,14 +66,18 @@ class ClassWithoutDecorators {
class TestObj {
constructor(public a: any, public b: any) {}
identity(arg: any) { return arg; }
identity(arg: any) {
return arg;
}
}
{
describe('Reflector', () => {
let reflector: Reflector;
beforeEach(() => { reflector = new Reflector(new ReflectionCapabilities()); });
beforeEach(() => {
reflector = new Reflector(new ReflectionCapabilities());
});
describe('factory', () => {
it('should create a factory for the given type', () => {
@ -131,8 +137,7 @@ class TestObj {
it('should also return metadata if the class has no decorator', () => {
class Test {
@PropDecorator('test')
prop: any;
@PropDecorator('test') prop: any;
}
expect(reflector.propMetadata(Test)).toEqual({'prop': [new PropDecorator('test')]});
@ -183,7 +188,9 @@ class TestObj {
class ChildNoCtor extends Parent {}
class ChildWithCtor extends Parent {
constructor() { super(); }
constructor() {
super();
}
}
class ChildNoCtorPrivateProps extends Parent {
private x = 10;
@ -242,12 +249,10 @@ class TestObj {
noCtor(`class $Bar1_ extends $Fo0_ {}`);
noCtor(`class Bar extends Foo { other(){} }`);
});
});
describe('inheritance with decorators', () => {
it('should inherit annotations', () => {
@ClassDecorator({value: 'parent'})
class Parent {
}
@ -273,7 +278,7 @@ class TestObj {
expect(reflector.annotations(NoDecorators)).toEqual([]);
expect(reflector.annotations(<any>{})).toEqual([]);
expect(reflector.annotations(<any>1)).toEqual([]);
expect(reflector.annotations(null !)).toEqual([]);
expect(reflector.annotations(null!)).toEqual([]);
});
it('should inherit parameters', () => {
@ -303,11 +308,15 @@ class TestObj {
// as otherwise TS won't capture the ctor arguments!
@ClassDecorator({value: 'child'})
class ChildWithCtor extends Parent {
constructor(@ParamDecorator('c') c: C) { super(null !, null !); }
constructor(@ParamDecorator('c') c: C) {
super(null!, null!);
}
}
class ChildWithCtorNoDecorator extends Parent {
constructor(a: any, b: any, c: any) { super(null !, null !); }
constructor(a: any, b: any, c: any) {
super(null!, null!);
}
}
class NoDecorators {}
@ -340,7 +349,7 @@ class TestObj {
expect(reflector.parameters(NoDecorators)).toEqual([]);
expect(reflector.parameters(<any>{})).toEqual([]);
expect(reflector.parameters(<any>1)).toEqual([]);
expect(reflector.parameters(null !)).toEqual([]);
expect(reflector.parameters(null!)).toEqual([]);
});
it('should inherit property metadata', () => {
@ -350,20 +359,16 @@ class TestObj {
class Parent {
// TODO(issue/24571): remove '!'.
@PropDecorator('a')
a !: A;
@PropDecorator('a') a!: A;
// TODO(issue/24571): remove '!'.
@PropDecorator('b1')
b !: B;
@PropDecorator('b1') b!: B;
}
class Child extends Parent {
// TODO(issue/24571): remove '!'.
@PropDecorator('b2')
b !: B;
@PropDecorator('b2') b!: B;
// TODO(issue/24571): remove '!'.
@PropDecorator('c')
c !: C;
@PropDecorator('c') c!: C;
}
class NoDecorators {}
@ -383,7 +388,7 @@ class TestObj {
expect(reflector.propMetadata(NoDecorators)).toEqual({});
expect(reflector.propMetadata(<any>{})).toEqual({});
expect(reflector.propMetadata(<any>1)).toEqual({});
expect(reflector.propMetadata(null !)).toEqual({});
expect(reflector.propMetadata(null!)).toEqual({});
});
it('should inherit lifecycle hooks', () => {
@ -406,12 +411,10 @@ class TestObj {
expect(hooks(Child, ['hook1', 'hook2', 'hook3'])).toEqual([true, true, true]);
});
});
describe('inheritance with tsickle', () => {
it('should inherit annotations', () => {
class Parent {
static decorators = [{type: ClassDecorator, args: [{value: 'parent'}]}];
}
@ -448,9 +451,12 @@ class TestObj {
class Child extends Parent {}
class ChildWithCtor extends Parent {
static ctorParameters =
() => [{type: C, decorators: [{type: ParamDecorator, args: ['c']}]}, ]
constructor() { super(); }
static ctorParameters = () =>
[{type: C, decorators: [{type: ParamDecorator, args: ['c']}]},
]
constructor() {
super();
}
}
// Check that metadata for Parent was not changed!
@ -496,12 +502,10 @@ class TestObj {
'c': [new PropDecorator('c')]
});
});
});
describe('inheritance with es5 API', () => {
it('should inherit annotations', () => {
class Parent {
static annotations = [new ClassDecorator({value: 'parent'})];
}
@ -541,7 +545,9 @@ class TestObj {
static parameters = [
[C, new ParamDecorator('c')],
];
constructor() { super(); }
constructor() {
super();
}
}
// Check that metadata for Parent was not changed!

View File

@ -13,7 +13,6 @@ import {RenderFlags} from '../../src/render3/interfaces/definition';
import {document, renderComponent} from './render_util';
describe('iv perf test', () => {
const count = 100000;
const noOfIterations = 10;
@ -39,28 +38,29 @@ describe('iv perf test', () => {
selectors: [['div']],
decls: 1,
vars: 0,
template: function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
for (let i = 0; i < count; i++) {
let rf0 = ɵɵembeddedViewStart(0, 2, 0);
template:
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
if (rf0 & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, '-');
ɵɵelementEnd();
for (let i = 0; i < count; i++) {
let rf0 = ɵɵembeddedViewStart(0, 2, 0);
{
if (rf0 & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, '-');
ɵɵelementEnd();
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵembeddedViewEnd();
ɵɵcontainerRefreshEnd();
}
}
ɵɵcontainerRefreshEnd();
}
}
});
}

View File

@ -10,7 +10,7 @@ import {withBody} from '@angular/private/testing';
import {ChangeDetectionStrategy, DoCheck} from '../../src/core';
import {whenRendered} from '../../src/render3/component';
import {LifecycleHooksFeature, getRenderedText, ɵɵadvance, ɵɵdefineComponent, ɵɵgetCurrentView, ɵɵproperty, ɵɵtextInterpolate1, ɵɵtextInterpolate2} from '../../src/render3/index';
import {getRenderedText, LifecycleHooksFeature, ɵɵadvance, ɵɵdefineComponent, ɵɵgetCurrentView, ɵɵproperty, ɵɵtextInterpolate1, ɵɵtextInterpolate2} from '../../src/render3/index';
import {detectChanges, markDirty, tick, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵlistener, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer';
@ -23,7 +23,9 @@ describe('change detection', () => {
class MyComponent implements DoCheck {
value: string = 'works';
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
static ɵfac = () => new MyComponent();
static ɵcmp = ɵɵdefineComponent({
@ -31,17 +33,18 @@ describe('change detection', () => {
selectors: [['my-comp']],
decls: 2,
vars: 1,
template: (rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'span');
ɵɵtext(1);
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵadvance(1);
ɵɵtextInterpolate(ctx.value);
}
}
template:
(rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'span');
ɵɵtext(1);
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵadvance(1);
ɵɵtextInterpolate(ctx.value);
}
}
});
}
@ -78,7 +81,7 @@ describe('change detection', () => {
expect(myComp.doCheckCount).toBe(2);
}));
it('should notify whenRendered', withBody('my-comp', async() => {
it('should notify whenRendered', withBody('my-comp', async () => {
const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
await whenRendered(myComp);
myComp.value = 'updated';
@ -97,7 +100,9 @@ describe('change detection', () => {
name = 'Nancy';
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
onClick() {}
@ -111,19 +116,22 @@ describe('change detection', () => {
* {{ doCheckCount }} - {{ name }}
* <button (click)="onClick()"></button>
*/
template: (rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵelementStart(1, 'button');
{
ɵɵlistener('click', () => { ctx.onClick(); });
}
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate2('', ctx.doCheckCount, ' - ', ctx.name, '');
}
},
template:
(rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵelementStart(1, 'button');
{
ɵɵlistener('click', () => {
ctx.onClick();
});
}
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate2('', ctx.doCheckCount, ' - ', ctx.name, '');
}
},
changeDetection: ChangeDetectionStrategy.OnPush,
inputs: {name: 'name'}
});
@ -135,7 +143,9 @@ describe('change detection', () => {
name = 'Nancy';
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
onClick() {}
@ -149,24 +159,27 @@ describe('change detection', () => {
* {{ doCheckCount }} - {{ name }}
* <button (click)="onClick()"></button>
*/
template: (rf: RenderFlags, ctx: ManualComponent) => {
if (rf & RenderFlags.Create) {
// This is temporarily the only way to turn on manual change detection
// because public API has not yet been added.
const view = ɵɵgetCurrentView() as any;
view[FLAGS] |= LViewFlags.ManualOnPush;
template:
(rf: RenderFlags, ctx: ManualComponent) => {
if (rf & RenderFlags.Create) {
// This is temporarily the only way to turn on manual change detection
// because public API has not yet been added.
const view = ɵɵgetCurrentView() as any;
view[FLAGS] |= LViewFlags.ManualOnPush;
ɵɵtext(0);
ɵɵelementStart(1, 'button');
{
ɵɵlistener('click', () => { ctx.onClick(); });
}
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate2('', ctx.doCheckCount, ' - ', ctx.name, '');
}
},
ɵɵtext(0);
ɵɵelementStart(1, 'button');
{
ɵɵlistener('click', () => {
ctx.onClick();
});
}
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate2('', ctx.doCheckCount, ' - ', ctx.name, '');
}
},
changeDetection: ChangeDetectionStrategy.OnPush,
inputs: {name: 'name'}
});
@ -182,15 +195,15 @@ describe('change detection', () => {
decls: 1,
vars: 1,
/** <manual-comp [name]="name"></manual-comp> */
template: (rf: RenderFlags, ctx: ManualApp) => {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'manual-comp');
}
if (rf & RenderFlags.Update) {
ɵɵproperty('name', ctx.name);
}
},
template:
(rf: RenderFlags, ctx: ManualApp) => {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'manual-comp');
}
if (rf & RenderFlags.Update) {
ɵɵproperty('name', ctx.name);
}
},
directives: () => [ManualComponent]
});
}
@ -202,7 +215,7 @@ describe('change detection', () => {
expect(comp.doCheckCount).toEqual(1);
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
const button = containerEl.querySelector('button') !;
const button = containerEl.querySelector('button')!;
button.click();
requestAnimationFrame.flush();
// No ticks should have been scheduled.
@ -228,7 +241,9 @@ describe('change detection', () => {
class ButtonParent implements DoCheck {
doCheckCount = 0;
ngDoCheck(): void { this.doCheckCount++; }
ngDoCheck(): void {
this.doCheckCount++;
}
static ɵfac = () => parent = new ButtonParent();
static ɵcmp = ɵɵdefineComponent({
@ -237,15 +252,16 @@ describe('change detection', () => {
decls: 2,
vars: 1,
/** {{ doCheckCount }} - <manual-comp></manual-comp> */
template: (rf: RenderFlags, ctx: ButtonParent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵelement(1, 'manual-comp');
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate1('', ctx.doCheckCount, ' - ');
}
},
template:
(rf: RenderFlags, ctx: ButtonParent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵelement(1, 'manual-comp');
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate1('', ctx.doCheckCount, ' - ');
}
},
directives: () => [ManualComponent],
changeDetection: ChangeDetectionStrategy.OnPush
});
@ -258,35 +274,35 @@ describe('change detection', () => {
}, 1, 0, [ButtonParent]);
const myButtonApp = renderComponent(MyButtonApp);
expect(parent !.doCheckCount).toEqual(1);
expect(comp !.doCheckCount).toEqual(1);
expect(parent!.doCheckCount).toEqual(1);
expect(comp!.doCheckCount).toEqual(1);
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
tick(myButtonApp);
expect(parent !.doCheckCount).toEqual(2);
expect(parent!.doCheckCount).toEqual(2);
// parent isn't checked, so child doCheck won't run
expect(comp !.doCheckCount).toEqual(1);
expect(comp!.doCheckCount).toEqual(1);
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
const button = containerEl.querySelector('button');
button !.click();
button!.click();
requestAnimationFrame.flush();
// No ticks should have been scheduled.
expect(parent !.doCheckCount).toEqual(2);
expect(comp !.doCheckCount).toEqual(1);
expect(parent!.doCheckCount).toEqual(2);
expect(comp!.doCheckCount).toEqual(1);
tick(myButtonApp);
expect(parent !.doCheckCount).toEqual(3);
expect(parent!.doCheckCount).toEqual(3);
// parent isn't checked, so child doCheck won't run
expect(comp !.doCheckCount).toEqual(1);
expect(comp!.doCheckCount).toEqual(1);
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
markDirty(comp);
requestAnimationFrame.flush();
// Now that markDirty has been manually called, both views should be dirty and a tick
// should be scheduled to check the view.
expect(parent !.doCheckCount).toEqual(4);
expect(comp !.doCheckCount).toEqual(2);
expect(parent!.doCheckCount).toEqual(4);
expect(comp!.doCheckCount).toEqual(2);
expect(getRenderedText(myButtonApp)).toEqual('4 - 2 - Nancy');
});
});
@ -296,7 +312,9 @@ describe('change detection', () => {
const log: string[] = [];
const testRendererFactory: RendererFactory3 = {
createRenderer: (): Renderer3 => { return document; },
createRenderer: (): Renderer3 => {
return document;
},
begin: () => log.push('begin'),
end: () => log.push('end'),
};
@ -313,14 +331,15 @@ describe('change detection', () => {
selectors: [['my-comp']],
decls: 1,
vars: 1,
template: (rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.value);
}
}
template:
(rf: RenderFlags, ctx: MyComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.value);
}
}
});
}
@ -328,5 +347,4 @@ describe('change detection', () => {
expect(getRenderedText(myComp)).toEqual('works');
expect(log).toEqual(['begin', 'detect changes', 'end']);
});
});

View File

@ -9,7 +9,7 @@
import {NgForOf as NgForOfDef, NgIf as NgIfDef, NgTemplateOutlet as NgTemplateOutletDef} from '@angular/common';
import {IterableDiffers, NgIterable, TemplateRef, ViewContainerRef} from '@angular/core';
import {DirectiveType, ɵɵNgOnChangesFeature, ɵɵdefineDirective, ɵɵdirectiveInject} from '../../src/render3/index';
import {DirectiveType, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵNgOnChangesFeature} from '../../src/render3/index';
export const NgForOf: DirectiveType<NgForOfDef<any, NgIterable<any>>> = NgForOfDef as any;
export const NgIf: DirectiveType<NgIfDef<any>> = NgIfDef as any;
@ -42,8 +42,7 @@ NgTemplateOutlet.ɵdir = ɵɵdefineDirective({
type: NgTemplateOutletDef,
selectors: [['', 'ngTemplateOutlet', '']],
features: [ɵɵNgOnChangesFeature],
inputs:
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
inputs: {ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
});
NgTemplateOutlet.ɵfac = () => new NgTemplateOutletDef(ɵɵdirectiveInject(ViewContainerRef as any));

View File

@ -11,7 +11,7 @@ import {ComponentFactory} from '../../src/linker/component_factory';
import {RendererFactory2, RendererType2} from '../../src/render/api';
import {injectComponentFactoryResolver} from '../../src/render3/component_ref';
import {AttributeMarker, ɵɵdefineComponent} from '../../src/render3/index';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer';
import {Sanitizer} from '../../src/sanitization/sanitizer';
describe('ComponentFactory', () => {
@ -146,7 +146,7 @@ describe('ComponentFactory', () => {
{provide: RendererFactory2, useValue: {createRenderer: createRenderer3Spy}},
]);
cf.create(injector, undefined, undefined, { injector: mInjector } as NgModuleRef<any>);
cf.create(injector, undefined, undefined, {injector: mInjector} as NgModuleRef<any>);
expect(createRenderer2Spy).toHaveBeenCalled();
expect(createRenderer3Spy).not.toHaveBeenCalled();
@ -159,7 +159,7 @@ describe('ComponentFactory', () => {
{provide: RendererFactory2, useValue: {createRenderer: createRenderer2Spy}},
]);
cf.create(injector, undefined, undefined, { injector: mInjector } as NgModuleRef<any>);
cf.create(injector, undefined, undefined, {injector: mInjector} as NgModuleRef<any>);
expect(createRenderer2Spy).toHaveBeenCalled();
expect(createRenderer3Spy).not.toHaveBeenCalled();
@ -169,7 +169,7 @@ describe('ComponentFactory', () => {
const injector = Injector.create([]);
const mInjector = Injector.create([]);
cf.create(injector, undefined, undefined, { injector: mInjector } as NgModuleRef<any>);
cf.create(injector, undefined, undefined, {injector: mInjector} as NgModuleRef<any>);
expect(createRenderer2Spy).not.toHaveBeenCalled();
expect(createRenderer3Spy).toHaveBeenCalled();
@ -188,7 +188,7 @@ describe('ComponentFactory', () => {
{provide: Sanitizer, useFactory: mSanitizerFactorySpy, deps: []},
]);
cf.create(injector, undefined, undefined, { injector: mInjector } as NgModuleRef<any>);
cf.create(injector, undefined, undefined, {injector: mInjector} as NgModuleRef<any>);
expect(iSanitizerFactorySpy).toHaveBeenCalled();
expect(mSanitizerFactorySpy).not.toHaveBeenCalled();
@ -205,7 +205,7 @@ describe('ComponentFactory', () => {
]);
cf.create(injector, undefined, undefined, { injector: mInjector } as NgModuleRef<any>);
cf.create(injector, undefined, undefined, {injector: mInjector} as NgModuleRef<any>);
expect(mSanitizerFactorySpy).toHaveBeenCalled();
});

View File

@ -8,18 +8,20 @@
import {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core';
import {createInjector} from '../../src/di/r3_injector';
import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, getRenderedText, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵselect, ɵɵtemplate} from '../../src/render3/index';
import {AttributeMarker, ComponentFactory, getRenderedText, LifecycleHooksFeature, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵselect, ɵɵtemplate} from '../../src/render3/index';
import {tick, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵnextContext, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
import {ComponentDef, RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def';
import {ComponentFixture, MockRendererFactory, containerEl, createComponent, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
describe('component', () => {
class CounterComponent {
count = 0;
increment() { this.count++; }
increment() {
this.count++;
}
static ɵfac = () => new CounterComponent;
static ɵcmp = ɵɵdefineComponent({
@ -28,14 +30,15 @@ describe('component', () => {
selectors: [['counter']],
decls: 1,
vars: 1,
template: function(rf: RenderFlags, ctx: CounterComponent) {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.count);
}
},
template:
function(rf: RenderFlags, ctx: CounterComponent) {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.count);
}
},
inputs: {count: 'count'},
});
}
@ -78,14 +81,15 @@ describe('component', () => {
selectors: [['my-component']],
decls: 1,
vars: 1,
template: function(fs: RenderFlags, ctx: MyComponent) {
if (fs & RenderFlags.Create) {
ɵɵtext(0);
}
if (fs & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.myService.value);
}
}
template:
function(fs: RenderFlags, ctx: MyComponent) {
if (fs & RenderFlags.Create) {
ɵɵtext(0);
}
if (fs & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.myService.value);
}
}
});
}
@ -105,11 +109,9 @@ describe('component', () => {
const fixture = new ComponentFixture(MyComponent, {injector: createInjector(MyModule)});
expect(fixture.html).toEqual('injector');
});
});
it('should instantiate components at high indices', () => {
// {{ name }}
class Comp {
// @Input
@ -121,14 +123,15 @@ describe('component', () => {
selectors: [['comp']],
decls: 1,
vars: 1,
template: (rf: RenderFlags, ctx: Comp) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.name);
}
},
template:
(rf: RenderFlags, ctx: Comp) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.name);
}
},
inputs: {name: 'name'}
});
}
@ -152,7 +155,6 @@ describe('component', () => {
fixture.update();
expect(fixture.html).toEqual('<comp>some name</comp>');
});
});
it('should not invoke renderer destroy method for embedded views', () => {
@ -186,31 +188,32 @@ it('should not invoke renderer destroy method for embedded views', () => {
* <div>Root view</div>
* <div *ngIf="visible">Child view</div>
*/
template: function(rf: RenderFlags, ctx: Comp) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, 'Root view');
ɵɵelementEnd();
ɵɵtemplate(2, MyComponent_div_Template_2, 2, 0, 'div', 0);
}
if (rf & RenderFlags.Update) {
ɵɵadvance(2);
ɵɵproperty('ngIf', ctx.visible);
}
}
template:
function(rf: RenderFlags, ctx: Comp) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, 'Root view');
ɵɵelementEnd();
ɵɵtemplate(2, MyComponent_div_Template_2, 2, 0, 'div', 0);
}
if (rf & RenderFlags.Update) {
ɵɵadvance(2);
ɵɵproperty('ngIf', ctx.visible);
}
}
});
}
const rendererFactory = new MockRendererFactory(['destroy']);
const fixture = new ComponentFixture(Comp, {rendererFactory});
comp !.visible = false;
comp!.visible = false;
fixture.update();
comp !.visible = true;
comp!.visible = true;
fixture.update();
const renderer = rendererFactory.lastRenderer !;
const renderer = rendererFactory.lastRenderer!;
const destroySpy = renderer.spies['destroy'];
// we should never see `destroy` method being called
@ -246,7 +249,7 @@ describe('component with a container', () => {
class WrapperComponent {
// TODO(issue/24571): remove '!'.
items !: string[];
items!: string[];
static ɵfac = () => new WrapperComponent;
static ɵcmp = ɵɵdefineComponent({
type: WrapperComponent,
@ -254,20 +257,21 @@ describe('component with a container', () => {
selectors: [['wrapper']],
decls: 1,
vars: 0,
template: function ChildComponentTemplate(rf: RenderFlags, ctx: {items: string[]}) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
{ showItems(rf0, {items: ctx.items}); }
ɵɵembeddedViewEnd();
}
ɵɵcontainerRefreshEnd();
}
},
template:
function ChildComponentTemplate(rf: RenderFlags, ctx: {items: string[]}) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
{ showItems(rf0, {items: ctx.items}); }
ɵɵembeddedViewEnd();
}
ɵɵcontainerRefreshEnd();
}
},
inputs: {items: 'items'}
});
}
@ -320,9 +324,13 @@ describe('recursive components', () => {
class TreeComponent {
data: TreeNode = _buildTree(0);
ngDoCheck() { events.push('check' + this.data.value); }
ngDoCheck() {
events.push('check' + this.data.value);
}
ngOnDestroy() { events.push('destroy' + this.data.value); }
ngOnDestroy() {
events.push('destroy' + this.data.value);
}
static ɵfac = () => new TreeComponent();
static ɵcmp = ɵɵdefineComponent({
@ -331,46 +339,47 @@ describe('recursive components', () => {
selectors: [['tree-comp']],
decls: 3,
vars: 1,
template: (rf: RenderFlags, ctx: TreeComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵcontainer(1);
ɵɵcontainer(2);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.data.value);
ɵɵcontainerRefreshStart(1);
{
if (ctx.data.left != null) {
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'tree-comp');
}
if (rf0 & RenderFlags.Update) {
ɵɵselect(0);
ɵɵproperty('data', ctx.data.left);
}
ɵɵembeddedViewEnd();
template:
(rf: RenderFlags, ctx: TreeComponent) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵcontainer(1);
ɵɵcontainer(2);
}
}
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.data.right != null) {
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'tree-comp');
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.data.value);
ɵɵcontainerRefreshStart(1);
{
if (ctx.data.left != null) {
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'tree-comp');
}
if (rf0 & RenderFlags.Update) {
ɵɵselect(0);
ɵɵproperty('data', ctx.data.left);
}
ɵɵembeddedViewEnd();
}
}
if (rf0 & RenderFlags.Update) {
ɵɵselect(0);
ɵɵproperty('data', ctx.data.right);
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.data.right != null) {
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'tree-comp');
}
if (rf0 & RenderFlags.Update) {
ɵɵselect(0);
ɵɵproperty('data', ctx.data.right);
}
ɵɵembeddedViewEnd();
}
}
ɵɵembeddedViewEnd();
ɵɵcontainerRefreshEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
},
inputs: {data: 'data'}
});
}
@ -385,9 +394,13 @@ describe('recursive components', () => {
class NgIfTree {
data: TreeNode = _buildTree(0);
ngDoCheck() { events.push('check' + this.data.value); }
ngDoCheck() {
events.push('check' + this.data.value);
}
ngOnDestroy() { events.push('destroy' + this.data.value); }
ngOnDestroy() {
events.push('destroy' + this.data.value);
}
static ɵfac = () => new NgIfTree();
static ɵcmp = ɵɵdefineComponent({
@ -397,20 +410,21 @@ describe('recursive components', () => {
decls: 3,
vars: 3,
consts: [[AttributeMarker.Bindings, 'data', AttributeMarker.Template, 'ngIf']],
template: (rf: RenderFlags, ctx: NgIfTree) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵtemplate(1, IfTemplate, 1, 1, 'ng-if-tree', 0);
ɵɵtemplate(2, IfTemplate2, 1, 1, 'ng-if-tree', 0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.data.value);
ɵɵadvance(1);
ɵɵproperty('ngIf', ctx.data.left);
ɵɵadvance(1);
ɵɵproperty('ngIf', ctx.data.right);
}
},
template:
(rf: RenderFlags, ctx: NgIfTree) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
ɵɵtemplate(1, IfTemplate, 1, 1, 'ng-if-tree', 0);
ɵɵtemplate(2, IfTemplate2, 1, 1, 'ng-if-tree', 0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.data.value);
ɵɵadvance(1);
ɵɵproperty('ngIf', ctx.data.left);
ɵɵadvance(1);
ɵɵproperty('ngIf', ctx.data.right);
}
},
inputs: {data: 'data'},
});
}
@ -457,7 +471,6 @@ describe('recursive components', () => {
// This tests that the view tree is set up properly for recursive components
it('should call onDestroys properly', () => {
/**
* % if (!skipContent) {
* <tree-comp></tree-comp>
@ -526,10 +539,10 @@ describe('recursive components', () => {
['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']);
});
it('should map inputs minified & unminified names', async() => {
it('should map inputs minified & unminified names', async () => {
class TestInputsComponent {
// TODO(issue/24571): remove '!'.
minifiedName !: string;
minifiedName!: string;
static ɵfac = () => new TestInputsComponent();
static ɵcmp = ɵɵdefineComponent({
type: TestInputsComponent,
@ -538,9 +551,10 @@ describe('recursive components', () => {
inputs: {minifiedName: 'unminifiedName'},
decls: 0,
vars: 0,
template: function(rf: RenderFlags, ctx: TestInputsComponent): void {
// Template not needed for this test
}
template: function(rf: RenderFlags, ctx: TestInputsComponent):
void {
// Template not needed for this test
}
});
}
@ -549,7 +563,5 @@ describe('recursive components', () => {
expect([
{propName: 'minifiedName', templateName: 'unminifiedName'}
]).toEqual(testInputsComponentFactory.inputs);
});
});

View File

@ -10,7 +10,7 @@ import {ɵɵdefineComponent} from '../../src/render3/definition';
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵselect, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {ComponentFixture, TemplateFixture, createComponent} from './render_util';
import {ComponentFixture, createComponent, TemplateFixture} from './render_util';
describe('JS control flow', () => {
it('should work with if block', () => {
@ -151,9 +151,10 @@ describe('JS control flow', () => {
});
it('should work with nested adjacent if blocks', () => {
const ctx: {condition: boolean,
condition2: boolean,
condition3: boolean} = {condition: true, condition2: false, condition3: true};
const ctx:
{condition: boolean,
condition2: boolean,
condition3: boolean} = {condition: true, condition2: false, condition3: true};
/**
* % if(ctx.condition) {
@ -165,7 +166,9 @@ describe('JS control flow', () => {
* % }
* % }
*/
function createTemplate() { ɵɵcontainer(0); }
function createTemplate() {
ɵɵcontainer(0);
}
function updateTemplate() {
ɵɵcontainerRefreshStart(0);
@ -174,7 +177,9 @@ describe('JS control flow', () => {
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
{
if (rf1 & RenderFlags.Create) {
{ ɵɵcontainer(0); }
{
ɵɵcontainer(0);
}
{ ɵɵcontainer(1); }
}
if (rf1 & RenderFlags.Update) {
@ -222,14 +227,14 @@ describe('JS control flow', () => {
it('should work with adjacent if blocks managing views in the same container', () => {
/**
* % if(ctx.condition1) {
* 1
* % }; if(ctx.condition2) {
* 2
* % }; if(ctx.condition3) {
* 3
* % }
*/
* % if(ctx.condition1) {
* 1
* % }; if(ctx.condition2) {
* 2
* % }; if(ctx.condition3) {
* 3
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
@ -539,15 +544,15 @@ describe('JS control flow', () => {
* <div>
* Before
* % for (let i = 0; i < cafes.length; i++) {
* <h2> {{ cafes[i].name }} </h2>
* % for (let j = 0; j < cafes[i].entrees.length; j++) {
* <h3> {{ cafes[i].entrees[j].name }} </h3>
* % for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) {
* {{ cafes[i].entrees[j].foods[k] }}
* % }
* % }
* -
* % }
* <h2> {{ cafes[i].name }} </h2>
* % for (let j = 0; j < cafes[i].entrees.length; j++) {
* <h3> {{ cafes[i].entrees[j].name }} </h3>
* % for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) {
* {{ cafes[i].entrees[j].foods[k] }}
* % }
* % }
* -
* % }
* After
* <div>
*/
@ -718,37 +723,38 @@ describe('JS control flow', () => {
selectors: [['app']],
decls: 3,
vars: 0,
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div');
ɵɵcontainer(1);
ɵɵcontainer(2);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(1);
{
if (ctx.condition) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
template:
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div');
ɵɵcontainer(1);
ɵɵcontainer(2);
}
}
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.condition2) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(1);
{
if (ctx.condition) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
}
}
ɵɵembeddedViewEnd();
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.condition2) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
},
directives: () => [Comp]
});
}
@ -788,37 +794,38 @@ describe('JS control flow', () => {
selectors: [['app']],
decls: 3,
vars: 0,
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div');
ɵɵcontainer(1);
ɵɵcontainer(2);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(1);
{
if (ctx.condition) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
template:
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div');
ɵɵcontainer(1);
ɵɵcontainer(2);
}
}
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.condition2) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(1);
{
if (ctx.condition) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
}
}
ɵɵembeddedViewEnd();
ɵɵcontainerRefreshEnd();
ɵɵcontainerRefreshStart(2);
{
if (ctx.condition2) {
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'comp');
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
},
directives: () => [Comp]
});
}
@ -902,7 +909,7 @@ describe('function calls', () => {
it('should work', () => {
let data: string[] = ['foo', 'bar'];
function spanify(rf: RenderFlags, ctx: {message: string | null}) {
function spanify(rf: RenderFlags, ctx: {message: string|null}) {
const message = ctx.message;
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'span');
@ -950,6 +957,5 @@ describe('function calls', () => {
data = [];
fixture.update();
expect(fixture.html).toEqual('<div>Before<span></span><span></span>After</div>');
});
});

View File

@ -11,7 +11,7 @@ import {createLView, createTView, getOrCreateTNode} from '@angular/core/src/rend
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {ɵɵdefineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
import {bloomAdd, bloomHashBitOrFactory as bloomHash, bloomHasToken, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
import {ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext} from '../../src/render3/index';
import {TNODE} from '../../src/render3/interfaces/injector';
import {TNodeType} from '../../src/render3/interfaces/node';
@ -24,7 +24,6 @@ import {ComponentFixture, createComponent, createDirective} from './render_util'
describe('di', () => {
describe('directive injection', () => {
class DirB {
value = 'DirB';
@ -34,10 +33,9 @@ describe('di', () => {
}
describe('flags', () => {
class DirB {
// TODO(issue/24571): remove '!'.
value !: string;
value!: string;
static ɵfac = () => new DirB();
static ɵdir =
@ -62,7 +60,6 @@ describe('di', () => {
beforeEach(() => dirA = null);
it('should not throw if dependency is @Optional (limp mode)', () => {
/** <div dirA></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
@ -70,8 +67,10 @@ describe('di', () => {
}
}, 1, 0, [DirA, DirB], [], undefined, [], [], undefined, [['dirA', '']]);
expect(() => { new ComponentFixture(App); }).not.toThrow();
expect(dirA !.dirB).toEqual(null);
expect(() => {
new ComponentFixture(App);
}).not.toThrow();
expect(dirA!.dirB).toEqual(null);
});
});
@ -123,11 +122,12 @@ describe('di', () => {
selectors: [['my-comp']],
decls: 1,
vars: 0,
template: function(rf: RenderFlags, ctx: MyComp) {
if (rf & RenderFlags.Create) {
ɵɵtext(0, 'Foo');
}
}
template:
function(rf: RenderFlags, ctx: MyComp) {
if (rf & RenderFlags.Create) {
ɵɵtext(0, 'Foo');
}
}
});
}
@ -137,8 +137,9 @@ describe('di', () => {
expect(isProceduralRenderer(fixture.component.renderer)).toBeTruthy();
});
it('should throw when injecting Renderer2 but the application is using Renderer3',
() => { expect(() => new ComponentFixture(MyComp)).toThrow(); });
it('should throw when injecting Renderer2 but the application is using Renderer3', () => {
expect(() => new ComponentFixture(MyComp)).toThrow();
});
});
describe('ɵɵinject', () => {
@ -148,7 +149,9 @@ describe('di', () => {
mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstCreatePass: true};
});
function bloomState() { return mockTView.data.slice(0, TNODE).reverse(); }
function bloomState() {
return mockTView.data.slice(0, TNODE).reverse();
}
class Dir0 {
/** @internal */ static __NG_ELEMENT_ID__ = 0;
@ -232,7 +235,7 @@ describe('di', () => {
// Simulate the situation where the previous parent is not initialized.
// This happens on first bootstrap because we don't init existing values
// so that we have smaller HelloWorld.
(parentTNode as{parent: any}).parent = undefined;
(parentTNode as {parent: any}).parent = undefined;
const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView);
expect(injector).not.toEqual(-1);
@ -241,5 +244,4 @@ describe('di', () => {
}
});
});
});

View File

@ -15,7 +15,9 @@ import {getNativeByIndex} from '../../src/render3/util/view_utils';
import {TemplateFixture} from './render_util';
describe('Runtime i18n', () => {
afterEach(() => { setDelayProjection(false); });
afterEach(() => {
setDelayProjection(false);
});
describe('getTranslationForTemplate', () => {
it('should crop messages for the selected template', () => {
let message = `simple text`;
@ -69,10 +71,12 @@ describe('Runtime i18n', () => {
const MSG_DIV = `simple text`;
const nbConsts = 1;
const index = 0;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
// Check debug
const debugOps = (opCodes as any).create.debug !.operations;
const debugOps = (opCodes as any).create.debug!.operations;
expect(debugOps[0].__raw_opCode).toBe('simple text');
expect(debugOps[0].type).toBe('Create Text Node');
expect(debugOps[0].nodeIndex).toBe(1);
@ -100,7 +104,9 @@ describe('Runtime i18n', () => {
const index = 1;
const elementIndex = 2;
const elementIndex2 = 3;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 5,
@ -136,7 +142,9 @@ describe('Runtime i18n', () => {
const MSG_DIV = `Hello <20>0<EFBFBD>!`;
const nbConsts = 2;
const index = 1;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect((opCodes as any).update.debug.operations).toEqual([
{__raw_opCode: 8, checkBit: 1, type: 'Text', nodeIndex: 2, text: 'Hello <20>0<EFBFBD>!'}
@ -161,7 +169,9 @@ describe('Runtime i18n', () => {
const MSG_DIV = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
const nbConsts = 2;
const index = 1;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 1,
@ -195,7 +205,9 @@ describe('Runtime i18n', () => {
let index = 1;
const firstTextNode = 3;
const rootTemplate = 2;
let opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
let opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 2,
@ -225,7 +237,9 @@ describe('Runtime i18n', () => {
index = 0;
const spanElement = 1;
const bElementSubTemplate = 2;
opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV, 1); }, null, nbConsts, index);
opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV, 1);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 2,
@ -252,7 +266,9 @@ describe('Runtime i18n', () => {
nbConsts = 2;
index = 0;
const bElement = 1;
opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV, 2); }, null, nbConsts, index);
opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV, 2);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 1,
@ -277,7 +293,9 @@ describe('Runtime i18n', () => {
}`;
const nbConsts = 1;
const index = 0;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
const tIcuIndex = 0;
const icuCommentNodeIndex = index + 1;
const firstTextNodeIndex = index + 2;
@ -465,7 +483,9 @@ describe('Runtime i18n', () => {
}`;
const nbConsts = 1;
const index = 0;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
const icuCommentNodeIndex = index + 1;
const firstTextNodeIndex = index + 2;
const nestedIcuCommentNodeIndex = index + 3;
@ -497,18 +517,18 @@ describe('Runtime i18n', () => {
cases: ['cat', 'dog', 'other'],
create: [
[
'cats', nestedTextNodeIndex, nestedIcuCommentNodeIndex
<< I18nMutateOpCode.SHIFT_PARENT |
'cats', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
'dogs', nestedTextNodeIndex, nestedIcuCommentNodeIndex
<< I18nMutateOpCode.SHIFT_PARENT |
'dogs', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
'animals', nestedTextNodeIndex, nestedIcuCommentNodeIndex
<< I18nMutateOpCode.SHIFT_PARENT |
'animals', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
]
],
@ -599,8 +619,9 @@ describe('Runtime i18n', () => {
const MSG_div_attr = ['title', MSG_title];
const nbConsts = 2;
const index = 1;
const opCodes =
getOpCodes(() => { ɵɵi18nAttributes(index, MSG_div_attr); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b1, // bindings mask
@ -616,8 +637,9 @@ describe('Runtime i18n', () => {
const MSG_div_attr = ['title', MSG_title];
const nbConsts = 2;
const index = 1;
const opCodes =
getOpCodes(() => { ɵɵi18nAttributes(index, MSG_div_attr); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b11, // bindings mask
@ -632,8 +654,9 @@ describe('Runtime i18n', () => {
const MSG_div_attr = ['title', MSG_title, 'aria-label', MSG_title];
const nbConsts = 2;
const index = 1;
const opCodes =
getOpCodes(() => { ɵɵi18nAttributes(index, MSG_div_attr); }, null, nbConsts, index);
const opCodes = getOpCodes(() => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b1, // bindings mask

View File

@ -17,9 +17,13 @@ import {EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event
import {isTextNode} from '@angular/platform-browser/testing/src/browser_util';
export class SimpleDomEventsPlugin extends EventManagerPlugin {
constructor(doc: any) { super(doc); }
constructor(doc: any) {
super(doc);
}
supports(eventName: string): boolean { return true; }
supports(eventName: string): boolean {
return true;
}
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
let callback: EventListener = handler as EventListener;

View File

@ -21,13 +21,13 @@ describe('lView_debug', () => {
afterEach(() => leaveView());
describe('TNode', () => {
let tNode !: TNodeDebug;
let tView !: TView;
let tNode!: TNodeDebug;
let tView!: TView;
beforeEach(() => {
tView = createTView(TViewType.Component, 0, null, 0, 0, null, null, null, null, null);
tNode = createTNode(tView, null !, TNodeType.Element, 0, '', null) as TNodeDebug;
tNode = createTNode(tView, null!, TNodeType.Element, 0, '', null) as TNodeDebug;
});
afterEach(() => tNode = tView = null !);
afterEach(() => tNode = tView = null!);
describe('styling', () => {
it('should decode no styling', () => {

Some files were not shown because too many files have changed in this diff Show More