/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {NgIf} from '@angular/common'; import {Component, Injectable, Input, NgModule, Pipe} from '@angular/core'; import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, withModule} from '@angular/core/testing'; import {AsyncTestCompleter, TestComponentBuilder, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; import {expect} from '@angular/platform-browser/testing/matchers'; import {ViewMetadata} from '../core_private'; @Component( {selector: 'child-comp', template: `Original {{childBinding}}`, directives: []}) @Injectable() class ChildComp { childBinding: string; constructor() { this.childBinding = 'Child'; } } @Component({selector: 'child-comp', template: `Mock`}) @Injectable() class MockChildComp { } @Component({ selector: 'parent-comp', template: `Parent()`, directives: [ChildComp] }) @Injectable() class ParentComp { } @Component({ selector: 'my-if-comp', template: `MyIf(More)`, directives: [NgIf] }) @Injectable() class MyIfComp { showMore: boolean = false; } @Component({selector: 'child-child-comp', template: `ChildChild`}) @Injectable() class ChildChildComp { } @Component({ selector: 'child-comp', template: `Original {{childBinding}}()`, directives: [ChildChildComp] }) @Injectable() class ChildWithChildComp { childBinding: string; constructor() { this.childBinding = 'Child'; } } @Component({selector: 'child-child-comp', template: `ChildChild Mock`}) @Injectable() class MockChildChildComp { } @Component({selector: 'autodetect-comp', template: `{{text}}`}) class AutoDetectComp { text: string = '1'; click() { this.text += '1'; } } @Component({selector: 'async-comp', template: `{{text}}`}) class AsyncComp { text: string = '1'; click() { Promise.resolve(null).then((_) => { this.text += '1'; }); } } @Component({selector: 'async-child-comp', template: '{{localText}}'}) class AsyncChildComp { localText: string = ''; @Input() set text(value: string) { Promise.resolve(null).then((_) => { this.localText = value; }); } } @Component({ selector: 'async-change-comp', template: ``, directives: [AsyncChildComp] }) class AsyncChangeComp { text: string = '1'; click() { this.text += '1'; } } @Component({selector: 'async-timeout-comp', template: `{{text}}`}) class AsyncTimeoutComp { text: string = '1'; click() { setTimeout(() => { this.text += '1'; }, 10); } } @Component( {selector: 'nested-async-timeout-comp', template: `{{text}}`}) class NestedAsyncTimeoutComp { text: string = '1'; click() { setTimeout(() => { setTimeout(() => { this.text += '1'; }, 10); }, 10); } } class FancyService { value: string = 'real value'; } class MockFancyService extends FancyService { value: string = 'mocked out value'; } @Component({ selector: 'my-service-comp', providers: [FancyService], template: `injected value: {{fancyService.value}}` }) class TestBindingsComp { constructor(private fancyService: FancyService) {} } @Component({ selector: 'my-service-comp', viewProviders: [FancyService], template: `injected value: {{fancyService.value}}` }) class TestViewBindingsComp { constructor(private fancyService: FancyService) {} } @Component({selector: 'li1', template: `One`}) class ListDir1 { } @Component({selector: 'li1', template: `Alternate One`}) class ListDir1Alt { } @Component({selector: 'li2', template: `Two`}) class ListDir2 { } const LIST_CHILDREN = [ListDir1, ListDir2]; @Component({ selector: 'directive-list-comp', template: `()()`, directives: [LIST_CHILDREN] }) class DirectiveListComp { } export function main() { describe('test component builder', function() { it('should instantiate a component with valid DOM', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(ChildComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Original Child'); async.done(); }); })); it('should allow changing members of the component', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(MyIfComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf()'); componentFixture.componentInstance.showMore = true; componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf(More)'); async.done(); }); })); it('should override a template', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideTemplate(MockChildComp, 'Mock') .createAsync(MockChildComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Mock'); async.done(); }); })); it('should override a view', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideView( ChildComp, new ViewMetadata({template: 'Modified {{childBinding}}'})) .createAsync(ChildComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Modified Child'); async.done(); }); })); it('should override component dependencies', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideDirective(ParentComp, ChildComp, MockChildComp) .createAsync(ParentComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Parent(Mock)'); async.done(); }); })); it('should override items from a list', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideDirective(DirectiveListComp, ListDir1, ListDir1Alt) .createAsync(DirectiveListComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('(Alternate One)(Two)'); async.done(); }); })); it('should override child component\'s dependencies', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp) .overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp) .createAsync(ParentComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement) .toHaveText('Parent(Original Child(ChildChild Mock))'); async.done(); }); })); it('should override a provider', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideProviders( TestBindingsComp, [{provide: FancyService, useClass: MockFancyService}]) .createAsync(TestBindingsComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement) .toHaveText('injected value: mocked out value'); async.done(); }); })); it('should override a viewBinding', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.overrideViewProviders( TestViewBindingsComp, [{provide: FancyService, useClass: MockFancyService}]) .createAsync(TestViewBindingsComp) .then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement) .toHaveText('injected value: mocked out value'); async.done(); }); })); it('should create components synchronously', inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { let componentFixture = tcb.overrideTemplate(MockChildComp, 'Mock').createSync(MockChildComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Mock'); })); describe('ComponentFixture', () => { it('should auto detect changes if autoDetectChanges is called', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AutoDetectComp).then((componentFixture) => { expect(componentFixture.ngZone).not.toBeNull(); componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.isStable()).toBe(true); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); })); it('should auto detect changes if ComponentFixtureAutoDetect is provided as true', withModule({providers: [{provide: ComponentFixtureAutoDetect, useValue: true}]}) .inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AutoDetectComp).then((componentFixture) => { expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); })); it('should signal through whenStable when the fixture is stable (autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncComp).then((componentFixture) => { componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become stable // before checking for new value. expect(componentFixture.isStable()).toBe(false); componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should signal through isStable when the fixture is stable (no autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become stable // before checking. componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should wait for macroTask(setTimeout) while checking for whenStable ' + '(autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncTimeoutComp).then((componentFixture) => { componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become // stable before checking for new value. expect(componentFixture.isStable()).toBe(false); componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should wait for macroTask(setTimeout) while checking for whenStable ' + '(no autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncTimeoutComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become // stable before checking for new value. expect(componentFixture.isStable()).toBe(false); componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' + '(autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(NestedAsyncTimeoutComp).then((componentFixture) => { componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become // stable before checking for new value. expect(componentFixture.isStable()).toBe(false); componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' + '(no autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(NestedAsyncTimeoutComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); expect(componentFixture.nativeElement).toHaveText('1'); // Component is updated asynchronously. Wait for the fixture to become // stable before checking for new value. expect(componentFixture.isStable()).toBe(false); componentFixture.whenStable().then((waited) => { expect(waited).toBe(true); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); })); it('should stabilize after async task in change detection (autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncChangeComp).then((componentFixture) => { componentFixture.autoDetectChanges(); componentFixture.whenStable().then((_) => { expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); componentFixture.whenStable().then((_) => { expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); }); })); it('should stabilize after async task in change detection(no autoDetectChanges)', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(AsyncChangeComp).then((componentFixture) => { componentFixture.detectChanges(); componentFixture.whenStable().then((_) => { // Run detectChanges again so that stabilized value is reflected in the // DOM. componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); let element = componentFixture.debugElement.children[0]; dispatchEvent(element.nativeElement, 'click'); componentFixture.detectChanges(); componentFixture.whenStable().then((_) => { // Run detectChanges again so that stabilized value is reflected in // the DOM. componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('11'); async.done(); }); }); }); })); describe('No NgZone', () => { beforeEachProviders(() => [{provide: ComponentFixtureNoNgZone, useValue: true}]); it('calling autoDetectChanges raises an error', () => { inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(ChildComp).then((componentFixture) => { expect(() => { componentFixture.autoDetectChanges(); }) .toThrow( 'Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set!!'); async.done(); }); }); }); it('should instantiate a component with valid DOM', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(ChildComp).then((componentFixture) => { expect(componentFixture.ngZone).toBeNull(); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Original Child'); async.done(); }); })); it('should allow changing members of the component', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { tcb.createAsync(MyIfComp).then((componentFixture) => { componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf()'); componentFixture.componentInstance.showMore = true; componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf(More)'); async.done(); }); })); }); }); }); }