/** * @license * Copyright Google LLC 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 {CompilerConfig, ResourceLoader} from '@angular/compiler'; import {Compiler, Component, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core'; import {async, fakeAsync, getTestBed, inject, TestBed, tick, withModule} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing'; // Services, and components for the tests. @Component({selector: 'child-comp', template: `Original {{childBinding}}`}) @Injectable() class ChildComp { childBinding: string; constructor() { this.childBinding = 'Child'; } } @Component({selector: 'child-comp', template: `Mock`}) @Injectable() class MockChildComp { } @Component({ selector: 'parent-comp', template: `Parent()`, }) @Injectable() class ParentComp { } @Component({selector: 'my-if-comp', template: `MyIf(More)`}) @Injectable() class MyIfComp { showMore: boolean = false; } @Component({selector: 'child-child-comp', template: `ChildChild`}) @Injectable() class ChildChildComp { } @Component({ selector: 'child-comp', template: `Original {{childBinding}}()`, }) @Injectable() class ChildWithChildComp { childBinding: string; constructor() { this.childBinding = 'Child'; } } class FancyService { value: string = 'real value'; getAsyncValue() { return Promise.resolve('async value'); } getTimeoutValue() { return new Promise((resolve, reject) => setTimeout(() => resolve('timeout value'), 10)); } } class MockFancyService extends FancyService { value: string = 'mocked out value'; } @Component({ selector: 'my-service-comp', providers: [FancyService], template: `injected value: {{fancyService.value}}` }) class TestProvidersComp { constructor(private fancyService: FancyService) {} } @Component({ selector: 'my-service-comp', viewProviders: [FancyService], template: `injected value: {{fancyService.value}}` }) class TestViewProvidersComp { constructor(private fancyService: FancyService) {} } @Directive({selector: '[someDir]', host: {'[title]': 'someDir'}}) class SomeDirective { // TODO(issue/24571): remove '!'. @Input() someDir!: string; } @Pipe({name: 'somePipe'}) class SomePipe { transform(value: string) { return `transformed ${value}`; } } @Component({selector: 'comp', template: `
`}) class CompUsingModuleDirectiveAndPipe { } @NgModule() class SomeLibModule { } @Component({ selector: 'comp', templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html' }) class CompWithUrlTemplate { } const aTok = new InjectionToken('a'); const bTok = new InjectionToken('b'); { describe('public testing API', () => { describe('using the async helper with context passing', () => { type TestContext = {actuallyDone: boolean}; beforeEach(function(this: TestContext) { this.actuallyDone = false; }); afterEach(function(this: TestContext) { expect(this.actuallyDone).toEqual(true); }); it('should run normal tests', function(this: TestContext) { this.actuallyDone = true; }); it('should run normal async tests', function(this: TestContext, done) { setTimeout(() => { this.actuallyDone = true; done(); }, 0); }); it('should run async tests with tasks', async(function(this: TestContext) { setTimeout(() => this.actuallyDone = true, 0); })); it('should run async tests with promises', async(function(this: TestContext) { const p = new Promise((resolve, reject) => setTimeout(resolve, 10)); p.then(() => this.actuallyDone = true); })); }); describe('basic context passing to inject, fakeAsync and withModule helpers', () => { const moduleConfig = { providers: [FancyService], }; type TestContext = {contextModified: boolean}; beforeEach(function(this: TestContext) { this.contextModified = false; }); afterEach(function(this: TestContext) { expect(this.contextModified).toEqual(true); }); it('should pass context to inject helper', inject([], function(this: TestContext) { this.contextModified = true; })); it('should pass context to fakeAsync helper', fakeAsync(function(this: TestContext) { this.contextModified = true; })); it('should pass context to withModule helper - simple', withModule(moduleConfig, function(this: TestContext) { this.contextModified = true; })); it('should pass context to withModule helper - advanced', withModule(moduleConfig) .inject([FancyService], function(this: TestContext, service: FancyService) { expect(service.value).toBe('real value'); this.contextModified = true; })); it('should preserve context when async and inject helpers are combined', async(inject([], function(this: TestContext) { setTimeout(() => this.contextModified = true, 0); }))); it('should preserve context when fakeAsync and inject helpers are combined', fakeAsync(inject([], function(this: TestContext) { setTimeout(() => this.contextModified = true, 0); tick(1); }))); }); describe('using the test injector with the inject helper', () => { describe('setting up Providers', () => { beforeEach(() => { TestBed.configureTestingModule( {providers: [{provide: FancyService, useValue: new FancyService()}]}); it('should use set up providers', inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); })); it('should wait until returned promises', async(inject([FancyService], (service: FancyService) => { service.getAsyncValue().then((value) => expect(value).toEqual('async value')); service.getTimeoutValue().then((value) => expect(value).toEqual('timeout value')); }))); it('should allow the use of fakeAsync', fakeAsync(inject([FancyService], (service: FancyService) => { let value: string = undefined!; service.getAsyncValue().then((val) => value = val); tick(); expect(value).toEqual('async value'); }))); it('should allow use of "done"', (done) => { inject([FancyService], (service: FancyService) => { let count = 0; const id = setInterval(() => { count++; if (count > 2) { clearInterval(id); done(); } }, 5); })(); // inject needs to be invoked explicitly with (). }); describe('using beforeEach', () => { beforeEach(inject([FancyService], (service: FancyService) => { service.value = 'value modified in beforeEach'; })); it('should use modified providers', inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('value modified in beforeEach'); })); }); describe('using async beforeEach', () => { beforeEach(async(inject([FancyService], (service: FancyService) => { service.getAsyncValue().then((value) => service.value = value); }))); it('should use asynchronously modified value', inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('async value'); })); }); }); }); }); describe('using the test injector with modules', () => { const moduleConfig = { providers: [FancyService], imports: [SomeLibModule], declarations: [SomeDirective, SomePipe, CompUsingModuleDirectiveAndPipe], }; describe('setting up a module', () => { beforeEach(() => TestBed.configureTestingModule(moduleConfig)); it('should use set up providers', inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); })); it('should be able to create any declared components', () => { const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe); expect(compFixture.componentInstance).toBeAnInstanceOf(CompUsingModuleDirectiveAndPipe); }); it('should use set up directives and pipes', () => { const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe); const el = compFixture.debugElement; compFixture.detectChanges(); expect(el.children[0].properties['title']).toBe('transformed someValue'); }); it('should use set up imported modules', inject([SomeLibModule], (libModule: SomeLibModule) => { expect(libModule).toBeAnInstanceOf(SomeLibModule); })); describe('provided schemas', () => { @Component({template: ''}) class ComponentUsingInvalidProperty { } beforeEach(() => { TestBed.configureTestingModule( {schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]}); }); it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA', () => { expect(TestBed.createComponent(ComponentUsingInvalidProperty).componentInstance) .toBeAnInstanceOf(ComponentUsingInvalidProperty); }); }); }); describe('per test modules', () => { it('should use set up providers', withModule(moduleConfig).inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); })); it('should use set up directives and pipes', withModule(moduleConfig, () => { const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe); const el = compFixture.debugElement; compFixture.detectChanges(); expect(el.children[0].properties['title']).toBe('transformed someValue'); })); it('should use set up library modules', withModule(moduleConfig).inject([SomeLibModule], (libModule: SomeLibModule) => { expect(libModule).toBeAnInstanceOf(SomeLibModule); })); }); describe('components with template url', () => { beforeEach(async(() => { TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]}); TestBed.compileComponents(); })); isBrowser && it('should allow to createSync components with templateUrl after explicit async compilation', () => { const fixture = TestBed.createComponent(CompWithUrlTemplate); expect(fixture.nativeElement).toHaveText('from external template'); }); }); describe('overwriting metadata', () => { @Pipe({name: 'undefined'}) class SomePipe { transform(value: string): string { return `transformed ${value}`; } } @Directive({selector: '[undefined]'}) class SomeDirective { someProp = 'hello'; } @Component({selector: 'comp', template: 'someText'}) class SomeComponent { } @Component({selector: 'comp', template: 'someOtherText'}) class SomeOtherComponent { } @NgModule({declarations: [SomeComponent, SomeDirective, SomePipe]}) class SomeModule { } beforeEach(() => TestBed.configureTestingModule({imports: [SomeModule]})); describe('module', () => { beforeEach(() => { TestBed.overrideModule(SomeModule, {set: {declarations: [SomeOtherComponent]}}); }); it('should work', () => { expect(TestBed.createComponent(SomeOtherComponent).componentInstance) .toBeAnInstanceOf(SomeOtherComponent); }); }); describe('component', () => { beforeEach(() => { TestBed.overrideComponent( SomeComponent, {set: {selector: 'comp', template: 'newText'}}); }); it('should work', () => { expect(TestBed.createComponent(SomeComponent).nativeElement).toHaveText('newText'); }); }); describe('directive', () => { beforeEach(() => { TestBed .overrideComponent( SomeComponent, {set: {selector: 'comp', template: `
`}}) .overrideDirective( SomeDirective, {set: {selector: '[someDir]', host: {'[title]': 'someProp'}}}); }); it('should work', () => { const compFixture = TestBed.createComponent(SomeComponent); compFixture.detectChanges(); expect(compFixture.debugElement.children[0].properties['title']).toEqual('hello'); }); }); describe('pipe', () => { beforeEach(() => { TestBed .overrideComponent( SomeComponent, {set: {selector: 'comp', template: `{{'hello' | somePipe}}`}}) .overridePipe(SomePipe, {set: {name: 'somePipe'}}) .overridePipe(SomePipe, {add: {pure: false}}); }); it('should work', () => { const compFixture = TestBed.createComponent(SomeComponent); compFixture.detectChanges(); expect(compFixture.nativeElement).toHaveText('transformed hello'); }); }); describe('template', () => { let testBedSpy: any; beforeEach(() => { testBedSpy = spyOn(getTestBed(), 'overrideComponent').and.callThrough(); TestBed.overrideTemplate(SomeComponent, 'newText'); }); it(`should override component's template`, () => { const fixture = TestBed.createComponent(SomeComponent); expect(fixture.nativeElement).toHaveText('newText'); expect(testBedSpy).toHaveBeenCalledWith(SomeComponent, { set: {template: 'newText', templateUrl: null} }); }); }); }); describe('overriding providers', () => { describe('in core', () => { it('ComponentFactoryResolver', () => { const componentFactoryMock = jasmine.createSpyObj('componentFactory', ['resolveComponentFactory']); TestBed.overrideProvider(ComponentFactoryResolver, {useValue: componentFactoryMock}); expect(TestBed.get(ComponentFactoryResolver)).toEqual(componentFactoryMock); }); }); describe('in NgModules', () => { it('should support useValue', () => { TestBed.configureTestingModule({ providers: [ {provide: aTok, useValue: 'aValue'}, ] }); TestBed.overrideProvider(aTok, {useValue: 'mockValue'}); expect(TestBed.inject(aTok)).toBe('mockValue'); }); it('should support useFactory', () => { TestBed.configureTestingModule({ providers: [ {provide: 'dep', useValue: 'depValue'}, {provide: aTok, useValue: 'aValue'}, ] }); TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']}); expect(TestBed.inject(aTok)).toBe('mockA: depValue'); }); it('should support @Optional without matches', () => { TestBed.configureTestingModule({ providers: [ {provide: aTok, useValue: 'aValue'}, ] }); TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]}); expect(TestBed.inject(aTok)).toBe('mockA: null'); }); it('should support Optional with matches', () => { TestBed.configureTestingModule({ providers: [ {provide: 'dep', useValue: 'depValue'}, {provide: aTok, useValue: 'aValue'}, ] }); TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]}); expect(TestBed.inject(aTok)).toBe('mockA: depValue'); }); it('should support SkipSelf', () => { @NgModule({ providers: [ {provide: aTok, useValue: 'aValue'}, {provide: 'dep', useValue: 'depValue'}, ] }) class MyModule { } TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]}); TestBed.configureTestingModule( {providers: [{provide: 'dep', useValue: 'parentDepValue'}]}); const compiler = TestBed.inject(Compiler); const modFactory = compiler.compileModuleSync(MyModule); expect(modFactory.create(getTestBed()).injector.get(aTok)) .toBe('mockA: parentDepValue'); }); it('should keep imported NgModules eager', () => { let someModule: SomeModule|undefined; @NgModule() class SomeModule { constructor() { someModule = this; } } TestBed.configureTestingModule({ providers: [ {provide: aTok, useValue: 'aValue'}, ], imports: [SomeModule] }); TestBed.overrideProvider(aTok, {useValue: 'mockValue'}); expect(TestBed.inject(aTok)).toBe('mockValue'); expect(someModule).toBeAnInstanceOf(SomeModule); }); describe('injecting eager providers into an eager overwritten provider', () => { @NgModule({ providers: [ {provide: aTok, useFactory: () => 'aValue'}, {provide: bTok, useFactory: () => 'bValue'}, ] }) class MyModule { // NgModule is eager, which makes all of its deps eager constructor(@Inject(aTok) a: any, @Inject(bTok) b: any) {} } it('should inject providers that were declared before', () => { TestBed.configureTestingModule({imports: [MyModule]}); TestBed.overrideProvider( bTok, {useFactory: (a: string) => `mockB: ${a}`, deps: [aTok]}); expect(TestBed.inject(bTok)).toBe('mockB: aValue'); }); it('should inject providers that were declared afterwards', () => { TestBed.configureTestingModule({imports: [MyModule]}); TestBed.overrideProvider( aTok, {useFactory: (b: string) => `mockA: ${b}`, deps: [bTok]}); expect(TestBed.inject(aTok)).toBe('mockA: bValue'); }); }); }); describe('in Components', () => { it('should support useValue', () => { @Component({ template: '', providers: [ {provide: aTok, useValue: 'aValue'}, ] }) class MComp { } TestBed.overrideProvider(aTok, {useValue: 'mockValue'}); const ctx = TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp); expect(ctx.debugElement.injector.get(aTok)).toBe('mockValue'); }); it('should support useFactory', () => { @Component({ template: '', providers: [ {provide: 'dep', useValue: 'depValue'}, {provide: aTok, useValue: 'aValue'}, ] }) class MyComp { } TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']}); const ctx = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: depValue'); }); it('should support @Optional without matches', () => { @Component({ template: '', providers: [ {provide: aTok, useValue: 'aValue'}, ] }) class MyComp { } TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]}); const ctx = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: null'); }); it('should support Optional with matches', () => { @Component({ template: '', providers: [ {provide: 'dep', useValue: 'depValue'}, {provide: aTok, useValue: 'aValue'}, ] }) class MyComp { } TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]}); const ctx = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: depValue'); }); it('should support SkipSelf', () => { @Directive({ selector: '[myDir]', providers: [ {provide: aTok, useValue: 'aValue'}, {provide: 'dep', useValue: 'depValue'}, ] }) class MyDir { } @Component({ template: '
', providers: [ {provide: 'dep', useValue: 'parentDepValue'}, ] }) class MyComp { } TestBed.overrideProvider( aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]}); const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]}) .createComponent(MyComp); expect(ctx.debugElement.children[0].injector.get(aTok)).toBe('mockA: parentDepValue'); }); it('should support multiple providers in a template', () => { @Directive({ selector: '[myDir1]', providers: [ {provide: aTok, useValue: 'aValue1'}, ] }) class MyDir1 { } @Directive({ selector: '[myDir2]', providers: [ {provide: aTok, useValue: 'aValue2'}, ] }) class MyDir2 { } @Component({ template: '
', }) class MyComp { } TestBed.overrideProvider(aTok, {useValue: 'mockA'}); const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]}) .createComponent(MyComp); expect(ctx.debugElement.children[0].injector.get(aTok)).toBe('mockA'); expect(ctx.debugElement.children[1].injector.get(aTok)).toBe('mockA'); }); describe('injecting eager providers into an eager overwritten provider', () => { @Component({ template: '', providers: [ {provide: aTok, useFactory: () => 'aValue'}, {provide: bTok, useFactory: () => 'bValue'}, ] }) class MyComp { // Component is eager, which makes all of its deps eager constructor(@Inject(aTok) a: any, @Inject(bTok) b: any) {} } it('should inject providers that were declared before it', () => { TestBed.overrideProvider( bTok, {useFactory: (a: string) => `mockB: ${a}`, deps: [aTok]}); const ctx = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); expect(ctx.debugElement.injector.get(bTok)).toBe('mockB: aValue'); }); it('should inject providers that were declared after it', () => { TestBed.overrideProvider( aTok, {useFactory: (b: string) => `mockA: ${b}`, deps: [bTok]}); const ctx = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: bValue'); }); }); }); it('should reset overrides when the testing modules is resetted', () => { TestBed.overrideProvider(aTok, {useValue: 'mockValue'}); TestBed.resetTestingModule(); TestBed.configureTestingModule({providers: [{provide: aTok, useValue: 'aValue'}]}); expect(TestBed.inject(aTok)).toBe('aValue'); }); }); describe('overrideTemplateUsingTestingModule', () => { it('should compile the template in the context of the testing module', () => { @Component({selector: 'comp', template: 'a'}) class MyComponent { prop = 'some prop'; } let testDir: TestDir|undefined; @Directive({selector: '[test]'}) class TestDir { constructor() { testDir = this; } // TODO(issue/24571): remove '!'. @Input('test') test!: string; } TestBed.overrideTemplateUsingTestingModule( MyComponent, '
Hello world!
'); const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]}) .createComponent(MyComponent); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Hello world!'); expect(testDir).toBeAnInstanceOf(TestDir); expect(testDir!.test).toBe('some prop'); }); it('should throw if the TestBed is already created', () => { @Component({selector: 'comp', template: 'a'}) class MyComponent { } TestBed.inject(Injector); expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b')) .toThrowError( /Cannot override template when the test module has already been instantiated/); }); it('should reset overrides when the testing module is resetted', () => { @Component({selector: 'comp', template: 'a'}) class MyComponent { } TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b'); const fixture = TestBed.resetTestingModule() .configureTestingModule({declarations: [MyComponent]}) .createComponent(MyComponent); expect(fixture.nativeElement).toHaveText('a'); }); }); describe('setting up the compiler', () => { describe('providers', () => { it('should use set up providers', fakeAsync(() => { // Keeping this component inside the test is needed to make sure it's not resolved // prior to this test, thus having ɵcmp and a reference in resource // resolution queue. This is done to check external resoution logic in isolation by // configuring TestBed with the necessary ResourceLoader instance. @Component({ selector: 'comp', templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html' }) class InternalCompWithUrlTemplate { } const resourceLoaderGet = jasmine.createSpy('resourceLoaderGet') .and.returnValue(Promise.resolve('Hello world!')); TestBed.configureTestingModule({declarations: [InternalCompWithUrlTemplate]}); TestBed.configureCompiler( {providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]}); TestBed.compileComponents(); tick(); const compFixture = TestBed.createComponent(InternalCompWithUrlTemplate); expect(compFixture.nativeElement).toHaveText('Hello world!'); })); }); describe('useJit true', () => { beforeEach(() => TestBed.configureCompiler({useJit: true})); obsoleteInIvy('the Render3 compiler JiT mode is not configurable') .it('should set the value into CompilerConfig', inject([CompilerConfig], (config: CompilerConfig) => { expect(config.useJit).toBe(true); })); }); describe('useJit false', () => { beforeEach(() => TestBed.configureCompiler({useJit: false})); obsoleteInIvy('the Render3 compiler JiT mode is not configurable') .it('should set the value into CompilerConfig', inject([CompilerConfig], (config: CompilerConfig) => { expect(config.useJit).toBe(false); })); }); }); }); describe('errors', () => { let originalJasmineIt: (description: string, func: () => void) => jasmine.Spec; let originalJasmineBeforeEach: (beforeEachFunction: (done: DoneFn) => void) => void; const patchJasmineIt = () => { let resolve: (result: any) => void; let reject: (error: any) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); originalJasmineIt = jasmine.getEnv().it; jasmine.getEnv().it = (description: string, fn: (done: DoneFn) => void): any => { const done = (() => resolve(null)); done.fail = (err) => reject(err); fn(done); return null; }; return promise; }; const restoreJasmineIt = () => jasmine.getEnv().it = originalJasmineIt; const patchJasmineBeforeEach = () => { let resolve: (result: any) => void; let reject: (error: any) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); originalJasmineBeforeEach = jasmine.getEnv().beforeEach; jasmine.getEnv().beforeEach = (fn: (done: DoneFn) => void) => { const done = (() => resolve(null)); done.fail = (err) => reject(err); fn(done); }; return promise; }; const restoreJasmineBeforeEach = () => jasmine.getEnv().beforeEach = originalJasmineBeforeEach; it('should fail when an asynchronous error is thrown', (done) => { const itPromise = patchJasmineIt(); const barError = new Error('bar'); it('throws an async error', async(inject([], () => setTimeout(() => { throw barError; }, 0)))); itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => { expect(err).toEqual(barError); done(); }); restoreJasmineIt(); }); it('should fail when a returned promise is rejected', (done) => { const itPromise = patchJasmineIt(); it('should fail with an error from a promise', async(inject([], () => { let reject: (error: any) => void = undefined!; const promise = new Promise((_, rej) => reject = rej); const p = promise.then(() => expect(1).toEqual(2)); reject('baz'); return p; }))); itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => { expect(err.message).toEqual('Uncaught (in promise): baz'); done(); }); restoreJasmineIt(); }); describe('components', () => { let resourceLoaderGet: jasmine.Spy; beforeEach(() => { resourceLoaderGet = jasmine.createSpy('resourceLoaderGet') .and.returnValue(Promise.resolve('Hello world!')); TestBed.configureCompiler( {providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]}); }); it('should report an error for declared components with templateUrl which never call TestBed.compileComponents', () => { const itPromise = patchJasmineIt(); @Component({ selector: 'comp', templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html' }) class InlineCompWithUrlTemplate { } expect( () => it('should fail', withModule( {declarations: [InlineCompWithUrlTemplate]}, () => TestBed.createComponent(InlineCompWithUrlTemplate)))) .toThrowError( ivyEnabled ? `Component 'InlineCompWithUrlTemplate' is not resolved: - templateUrl: /base/angular/packages/platform-browser/test/static_assets/test.html Did you run and wait for 'resolveComponentResources()'?` : `This test module uses the component ${ stringify( InlineCompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` + `Please call "TestBed.compileComponents" before your test.`); restoreJasmineIt(); }); }); modifiedInIvy(`Unknown property error thrown instead of logging a message`) .it('should error on unknown bound properties on custom elements by default', () => { @Component({template: ''}) class ComponentUsingInvalidProperty { } const itPromise = patchJasmineIt(); expect( () => it('should fail', withModule( {declarations: [ComponentUsingInvalidProperty]}, () => TestBed.createComponent(ComponentUsingInvalidProperty)))) .toThrowError(/Can't bind to 'someUnknownProp'/); restoreJasmineIt(); }); onlyInIvy(`Unknown property error logged instead of throwing`) .it('should error on unknown bound properties on custom elements by default', () => { @Component({template: '
'}) class ComponentUsingInvalidProperty { } const spy = spyOn(console, 'error'); withModule({declarations: [ComponentUsingInvalidProperty]}, () => { const fixture = TestBed.createComponent(ComponentUsingInvalidProperty); fixture.detectChanges(); })(); expect(spy.calls.mostRecent().args[0]).toMatch(/Can't bind to 'someUnknownProp'/); }); }); describe('creating components', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ ChildComp, MyIfComp, ChildChildComp, ParentComp, TestProvidersComp, TestViewProvidersComp, ] }); }); it('should instantiate a component with valid DOM', async(() => { const fixture = TestBed.createComponent(ChildComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Original Child'); })); it('should allow changing members of the component', async(() => { const componentFixture = TestBed.createComponent(MyIfComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf()'); componentFixture.componentInstance.showMore = true; componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf(More)'); })); it('should override a template', async(() => { TestBed.overrideComponent(ChildComp, {set: {template: 'Mock'}}); const componentFixture = TestBed.createComponent(ChildComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Mock'); })); it('should override a provider', async(() => { TestBed.overrideComponent( TestProvidersComp, {set: {providers: [{provide: FancyService, useClass: MockFancyService}]}}); const componentFixture = TestBed.createComponent(TestProvidersComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value'); })); it('should override a viewProvider', async(() => { TestBed.overrideComponent( TestViewProvidersComp, {set: {viewProviders: [{provide: FancyService, useClass: MockFancyService}]}}); const componentFixture = TestBed.createComponent(TestViewProvidersComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value'); })); }); describe('using alternate components', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ MockChildComp, ParentComp, ] }); }); it('should override component dependencies', async(() => { const componentFixture = TestBed.createComponent(ParentComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Parent(Mock)'); })); }); }); }