/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {CommonModule, DOCUMENT} 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 {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'; import {ElementRef} from '@angular/core/src/linker/element_ref'; import {QueryList} from '@angular/core/src/linker/query_list'; 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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent, el} 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'; import {stringify} from '../../src/util/stringify'; const ANCHOR_ELEMENT = new InjectionToken('AnchorElement'); if (ivyEnabled) { describe('ivy', () => { declareTests(); }); } else { describe('jit', () => { declareTests({useJit: true}); }); describe('no jit', () => { declareTests({useJit: false}); }); } function declareTests(config?: {useJit: boolean}) { describe('integration tests', function() { beforeEach(() => { TestBed.configureCompiler({...config}); }); describe('react to record changes', function() { it('should consume text node changes', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
{{ctxProp}}
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Hello World!'; fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Hello World!'); }); it('should update text node with a blank string when interpolation evaluates to null', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
{{null}}{{ctxProp}}
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = null !; fixture.detectChanges(); expect(fixture.nativeElement).toHaveText(''); }); it('should allow both null and undefined in expressions', () => { const template = '
{{null == undefined}}|{{null === undefined}}
'; const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent(MyComp, {set: {template}}) .createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('true|false'); }); it('should support an arbitrary number of interpolations in an element', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = `
before{{'0'}}a{{'1'}}b{{'2'}}c{{'3'}}d{{'4'}}e{{'5'}}f{{'6'}}g{{'7'}}h{{'8'}}i{{'9'}}j{{'10'}}after
`; const fixture = TestBed.overrideComponent(MyComp, {set: {template}}).createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('before0a1b2c3d4e5f6g7h8i9j10after'); }); it('should use a blank string when interpolation evaluates to null or undefined with an arbitrary number of interpolations', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = `
0{{null}}a{{undefined}}b{{null}}c{{undefined}}d{{null}}e{{undefined}}f{{null}}g{{undefined}}h{{null}}i{{undefined}}j{{null}}1
`; const fixture = TestBed.overrideComponent(MyComp, {set: {template}}).createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('0abcdefghij1'); }); it('should consume element binding changes', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Hello World!'; fixture.detectChanges(); expect(getDOM().getProperty(fixture.debugElement.children[0].nativeElement, 'id')) .toEqual('Hello World!'); }); it('should consume binding to aria-* attributes', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Initial aria label'; fixture.detectChanges(); expect(getDOM().getAttribute(fixture.debugElement.children[0].nativeElement, 'aria-label')) .toEqual('Initial aria label'); fixture.componentInstance.ctxProp = 'Changed aria label'; fixture.detectChanges(); expect(getDOM().getAttribute(fixture.debugElement.children[0].nativeElement, 'aria-label')) .toEqual('Changed aria label'); }); it('should remove an attribute when attribute expression evaluates to null', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'bar'; fixture.detectChanges(); expect(getDOM().getAttribute(fixture.debugElement.children[0].nativeElement, 'foo')) .toEqual('bar'); fixture.componentInstance.ctxProp = null !; fixture.detectChanges(); expect(getDOM().hasAttribute(fixture.debugElement.children[0].nativeElement, 'foo')) .toBeFalsy(); }); it('should remove style when when style expression evaluates to null', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = '10'; fixture.detectChanges(); expect(getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'height')) .toEqual('10px'); fixture.componentInstance.ctxProp = null !; fixture.detectChanges(); expect(getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'height')) .toEqual(''); }); it('should consume binding to property names where attr name and property name do not match', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(getDOM().getProperty(fixture.debugElement.children[0].nativeElement, 'tabIndex')) .toEqual(0); fixture.componentInstance.ctxNumProp = 5; fixture.detectChanges(); expect(getDOM().getProperty(fixture.debugElement.children[0].nativeElement, 'tabIndex')) .toEqual(5); }); it('should consume binding to camel-cased properties', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(getDOM().getProperty(fixture.debugElement.children[0].nativeElement, 'readOnly')) .toBeFalsy(); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); expect(getDOM().getProperty(fixture.debugElement.children[0].nativeElement, 'readOnly')) .toBeTruthy(); }); it('should consume binding to innerHtml', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Some HTML'; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.debugElement.children[0].nativeElement)) .toEqual('Some HTML'); fixture.componentInstance.ctxProp = 'Some other
HTML
'; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.debugElement.children[0].nativeElement)) .toEqual('Some other
HTML
'); }); modifiedInIvy('Binding to the class property directly works differently') .it('should consume binding to className using class alias', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const nativeEl = fixture.debugElement.children[0].nativeElement; fixture.componentInstance.ctxProp = 'foo bar'; fixture.detectChanges(); expect(nativeEl).toHaveCssClass('foo'); expect(nativeEl).toHaveCssClass('bar'); expect(nativeEl).not.toHaveCssClass('initial'); }); it('should consume binding to htmlFor using for alias', () => { const template = ''; const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent(MyComp, {set: {template}}) .createComponent(MyComp); const nativeEl = fixture.debugElement.children[0].nativeElement; fixture.debugElement.componentInstance.ctxProp = 'foo'; fixture.detectChanges(); expect(getDOM().getProperty(nativeEl, 'htmlFor')).toBe('foo'); }); it('should consume directive watch expression change.', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); const template = '' + '
' + '
' + '
' + '
' + '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Hello World!'; fixture.detectChanges(); const containerSpan = fixture.debugElement.children[0]; expect(containerSpan.children[0].injector.get(MyDir).dirProp).toEqual('Hello World!'); expect(containerSpan.children[1].injector.get(MyDir).dirProp).toEqual('Hi there!'); expect(containerSpan.children[2].injector.get(MyDir).dirProp).toEqual('Hi there!'); expect(containerSpan.children[3].injector.get(MyDir).dirProp) .toEqual('One more Hello World!'); }); describe('pipes', () => { it('should support pipes in bindings', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'a'; fixture.detectChanges(); const dir = fixture.debugElement.children[0].references !['dir']; expect(dir.dirProp).toEqual('aa'); }); }); it('should support nested components.', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('hello'); }); // GH issue 328 - https://github.com/angular/angular/issues/328 it('should support different directive types on a single node', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp, MyDir]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Hello World!'; fixture.detectChanges(); const tc = fixture.debugElement.children[0]; expect(tc.injector.get(MyDir).dirProp).toEqual('Hello World!'); expect(tc.injector.get(ChildComp).dirProp).toEqual(null); }); it('should support directives where a binding attribute is not given', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); }); it('should execute a given directive once, even if specified multiple times', () => { TestBed.configureTestingModule( {declarations: [MyComp, DuplicateDir, DuplicateDir, [DuplicateDir, [DuplicateDir]]]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.nativeElement).toHaveText('noduplicate'); }); it('should support directives where a selector matches property binding', () => { TestBed.configureTestingModule({declarations: [MyComp, IdDir]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const idDir = tc.injector.get(IdDir); fixture.componentInstance.ctxProp = 'some_id'; fixture.detectChanges(); expect(idDir.id).toEqual('some_id'); fixture.componentInstance.ctxProp = 'other_id'; fixture.detectChanges(); expect(idDir.id).toEqual('other_id'); }); it('should support directives where a selector matches event binding', () => { TestBed.configureTestingModule({declarations: [MyComp, EventDir]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; expect(tc.injector.get(EventDir)).not.toBeNull(); }); it('should display correct error message for uninitialized @Output', () => { @Component({selector: 'my-uninitialized-output', template: '

It works!

'}) class UninitializedOutputComp { @Output() customEvent !: EventEmitter; } const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.configureTestingModule({declarations: [MyComp, UninitializedOutputComp]}); expect(() => TestBed.createComponent(MyComp)) .toThrowError('@Output customEvent not initialized in \'UninitializedOutputComp\'.'); }); it('should read directives metadata from their binding token', () => { TestBed.configureTestingModule({declarations: [MyComp, PrivateImpl, NeedsPublicApi]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); }); modifiedInIvy('Comment node order changed') .it('should support template directives via `` elements.', () => { TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); const template = '{{greeting}}'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement); // 1 template + 2 copies. expect(childNodesOfWrapper.length).toBe(3); expect(childNodesOfWrapper[1]).toHaveText('hello'); expect(childNodesOfWrapper[2]).toHaveText('again'); }); it('should not share empty context for template directives - issue #10045', () => { TestBed.configureTestingModule({declarations: [MyComp, PollutedContext, NoContext]}); const template = '{{foo}}{{foo}}'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('baz'); }); it('should not detach views in ViewContainers when the parent view is destroyed.', () => { TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); const template = '
{{greeting}}
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); const ngIfEl = fixture.debugElement.children[0]; const someViewport: SomeViewport = ngIfEl.childNodes .find( debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE) !.injector.get(SomeViewport); expect(someViewport.container.length).toBe(2); expect(ngIfEl.children.length).toBe(2); fixture.componentInstance.ctxBoolProp = false; fixture.detectChanges(); expect(someViewport.container.length).toBe(2); expect(fixture.debugElement.children.length).toBe(0); }); it('should use a comment while stamping out `` elements.', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent(MyComp, {set: {template: ''}}) .createComponent(MyComp); const childNodesOfWrapper = getDOM().childNodes(fixture.nativeElement); expect(childNodesOfWrapper.length).toBe(1); expect(getDOM().isCommentNode(childNodesOfWrapper[0])).toBe(true); }); it('should allow to transplant TemplateRefs into other ViewContainers', () => { TestBed.configureTestingModule({ declarations: [ MyComp, SomeDirective, CompWithHost, ToolbarComponent, ToolbarViewContainer, ToolbarPart ], imports: [CommonModule], schemas: [NO_ERRORS_SCHEMA], }); const template = '{{ctxProp}},{{toolbarProp}},'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'From myComp'; fixture.detectChanges(); expect(fixture.nativeElement) .toHaveText('TOOLBAR(From myComp,From toolbar,Component with an injected host)'); }); describe('reference bindings', () => { it('should assign a component to a ref-', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.debugElement.children[0].children[0].references !['alice']) .toBeAnInstanceOf(ChildComp); }); it('should assign a directive to a ref-', () => { TestBed.configureTestingModule({declarations: [MyComp, ExportDir]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.debugElement.children[0].children[0].references !['localdir']) .toBeAnInstanceOf(ExportDir); }); it('should assign a directive to a ref when it has multiple exportAs names', () => { TestBed.configureTestingModule( {declarations: [MyComp, DirectiveWithMultipleExportAsNames]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.debugElement.children[0].references !['x']) .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames); expect(fixture.debugElement.children[0].references !['y']) .toBeAnInstanceOf(DirectiveWithMultipleExportAsNames); }); it('should make the assigned component accessible in property bindings, even if they were declared before the component', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = '{{alice.ctxProp}}|{{alice.ctxProp}}|'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('hello|hello|hello'); }); it('should assign two component instances each with a ref-', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const pEl = fixture.debugElement.children[0]; 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); }); it('should assign the component instance to a ref- with shorthand syntax', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.debugElement.children[0].references !['alice']) .toBeAnInstanceOf(ChildComp); }); it('should assign the element instance to a user-defined variable', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
Hello
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const value = fixture.debugElement.children[0].children[0].references !['alice']; expect(value).not.toBe(null); expect(value.tagName.toLowerCase()).toEqual('div'); }); it('should assign the TemplateRef to a user-defined variable', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent( MyComp, {set: {template: ''}}) .createComponent(MyComp); const value = fixture.debugElement.childNodes[0].references !['alice']; expect(value.createEmbeddedView).toBeTruthy(); }); it('should preserve case', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildComp]}); const template = '

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.debugElement.children[0].children[0].references !['superAlice']) .toBeAnInstanceOf(ChildComp); }); }); describe('variables', () => { modifiedInIvy('Comment node order changed') .it('should allow to use variables in a for loop', () => { const template = '{{i}}-{{cmp.ctxProp}}'; const fixture = TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]}) .overrideComponent(MyComp, {set: {template}}) .createComponent(MyComp); fixture.detectChanges(); // Get the element at index 2, since index 0 is the . expect(getDOM().childNodes(fixture.nativeElement)[2]).toHaveText('1-hello'); }); }); describe('OnPush components', () => { it('should use ChangeDetectorRef to manually request a check', () => { TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const cmp = fixture.debugElement.children[0].references !['cmp']; fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); cmp.propagate(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(2); }); it('should be checked when its bindings got updated', () => { TestBed.configureTestingModule( {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const cmp = fixture.debugElement.children[0].references !['cmp']; fixture.componentInstance.ctxProp = 'one'; fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); fixture.componentInstance.ctxProp = 'two'; fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(2); }); if (getDOM().supportsDOMEvents()) { it('should allow to destroy a component from within a host event handler', fakeAsync(() => { TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithHostEvent]]]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); tick(); fixture.detectChanges(); const cmpEl = fixture.debugElement.children[0]; const cmp: PushCmpWithHostEvent = cmpEl.injector.get(PushCmpWithHostEvent); cmp.ctxCallback = (_: any) => fixture.destroy(); expect(() => cmpEl.triggerEventHandler('click', {})).not.toThrow(); })); } it('should be checked when an event is fired', () => { TestBed.configureTestingModule( {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const cmpEl = fixture.debugElement.children[0]; const cmp = cmpEl.componentInstance; fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); // regular element cmpEl.children[0].triggerEventHandler('click', {}); fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(2); // element inside of an *ngIf cmpEl.children[1].triggerEventHandler('click', {}); fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(3); // element inside a nested component cmpEl.children[2].children[0].triggerEventHandler('click', {}); fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(4); // host element cmpEl.triggerEventHandler('click', {}); fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(5); }); it('should not affect updating properties on the component', () => { TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const cmp = fixture.debugElement.children[0].references !['cmp']; fixture.componentInstance.ctxProp = 'one'; fixture.detectChanges(); expect(cmp.prop).toEqual('one'); fixture.componentInstance.ctxProp = 'two'; fixture.detectChanges(); expect(cmp.prop).toEqual('two'); }); it('should be checked when an async pipe requests a check', fakeAsync(() => { TestBed.configureTestingModule( {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); tick(); const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references !['cmp']; fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); fixture.detectChanges(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(1); cmp.resolve(2); tick(); fixture.detectChanges(); expect(cmp.numberOfChecks).toEqual(2); })); }); it('should create a component that injects an @Host', () => { TestBed.configureTestingModule({ declarations: [MyComp, SomeDirective, CompWithHost], schemas: [NO_ERRORS_SCHEMA], }); const template = `

`; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const childComponent = fixture.debugElement.children[0].children[0].children[0].references !['child']; expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective); }); it('should create a component that injects an @Host through viewcontainer directive', () => { TestBed.configureTestingModule({ declarations: [MyComp, SomeDirective, CompWithHost], schemas: [NO_ERRORS_SCHEMA], }); const template = `

`; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const tc = fixture.debugElement.children[0].children[0].children[0]; const childComponent = tc.references !['child']; expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective); }); it('should support events via EventEmitter on regular elements', async(() => { TestBed.configureTestingModule( {declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const emitter = tc.injector.get(DirectiveEmittingEvent); const listener = tc.injector.get(DirectiveListeningEvent); expect(listener.msg).toEqual(''); let eventCount = 0; emitter.event.subscribe({ next: () => { eventCount++; if (eventCount === 1) { expect(listener.msg).toEqual('fired !'); fixture.destroy(); emitter.fireEvent('fired again !'); } else { expect(listener.msg).toEqual('fired !'); } } }); emitter.fireEvent('fired !'); })); it('should support events via EventEmitter on template elements', async(() => { const fixture = TestBed .configureTestingModule( {declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]}) .overrideComponent(MyComp, { set: { template: '' } }) .createComponent(MyComp); const tc = fixture.debugElement.childNodes.find( debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE) !; const emitter = tc.injector.get(DirectiveEmittingEvent); const myComp = fixture.debugElement.injector.get(MyComp); const listener = tc.injector.get(DirectiveListeningEvent); myComp.ctxProp = ''; expect(listener.msg).toEqual(''); emitter.event.subscribe({ next: () => { expect(listener.msg).toEqual('fired !'); expect(myComp.ctxProp).toEqual('fired !'); } }); emitter.fireEvent('fired !'); })); it('should support [()] syntax', async(() => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const dir = tc.injector.get(DirectiveWithTwoWayBinding); fixture.componentInstance.ctxProp = 'one'; fixture.detectChanges(); expect(dir.control).toEqual('one'); dir.controlChange.subscribe( {next: () => { expect(fixture.componentInstance.ctxProp).toEqual('two'); }}); dir.triggerChange('two'); })); it('should support render events', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const listener = tc.injector.get(DirectiveListeningDomEvent); dispatchEvent(tc.nativeElement, 'domEvent'); expect(listener.eventTypes).toEqual([ 'domEvent', 'body_domEvent', 'document_domEvent', 'window_domEvent' ]); fixture.destroy(); listener.eventTypes = []; dispatchEvent(tc.nativeElement, 'domEvent'); expect(listener.eventTypes).toEqual([]); }); it('should support render global events', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const doc = TestBed.get(DOCUMENT); const tc = fixture.debugElement.children[0]; const listener = tc.injector.get(DirectiveListeningDomEvent); dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent'); expect(listener.eventTypes).toEqual(['window_domEvent']); listener.eventTypes = []; dispatchEvent(getDOM().getGlobalEventTarget(doc, 'document'), 'domEvent'); expect(listener.eventTypes).toEqual(['document_domEvent', 'window_domEvent']); fixture.destroy(); listener.eventTypes = []; dispatchEvent(getDOM().getGlobalEventTarget(doc, 'body'), 'domEvent'); expect(listener.eventTypes).toEqual([]); }); it('should support updating host element via hostAttributes on root elements', () => { @Component({host: {'role': 'button'}, template: ''}) class ComponentUpdatingHostAttributes { } TestBed.configureTestingModule({declarations: [ComponentUpdatingHostAttributes]}); const fixture = TestBed.createComponent(ComponentUpdatingHostAttributes); fixture.detectChanges(); expect(getDOM().getAttribute(fixture.debugElement.nativeElement, 'role')).toEqual('button'); }); it('should support updating host element via hostAttributes on host elements', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveUpdatingHostAttributes]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(getDOM().getAttribute(fixture.debugElement.children[0].nativeElement, 'role')) .toEqual('button'); }); it('should support updating host element via hostProperties', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveUpdatingHostProperties]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const updateHost = tc.injector.get(DirectiveUpdatingHostProperties); updateHost.id = 'newId'; fixture.detectChanges(); expect(getDOM().getProperty(tc.nativeElement, 'id')).toEqual('newId'); }); it('should not use template variables for expressions in hostProperties', () => { @Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) class DirectiveWithHostProps { id = 'one'; } const fixture = TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) .overrideComponent( MyComp, {set: {template: `
`}}) .createComponent(MyComp); fixture.detectChanges(); const tc = fixture.debugElement.children[0]; expect(tc.properties['id']).toBe('one'); expect(tc.properties['title']).toBe(undefined); }); it('should not allow pipes in hostProperties', () => { @Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}}) class DirectiveWithHostProps { } TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); expect(() => TestBed.createComponent(MyComp)) .toThrowError(/Host binding expression cannot contain pipes/); }); it('should not use template variables for expressions in hostListeners', () => { @Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}}) class DirectiveWithHostListener { id = 'one'; // TODO(issue/24571): remove '!'. receivedArgs !: any[]; doIt(...args: any[]) { this.receivedArgs = args; } } const fixture = TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]}) .overrideComponent( MyComp, {set: {template: `
`}}) .createComponent(MyComp); fixture.detectChanges(); const tc = fixture.debugElement.children[0]; tc.triggerEventHandler('click', {}); const dir: DirectiveWithHostListener = tc.injector.get(DirectiveWithHostListener); expect(dir.receivedArgs).toEqual(['one', undefined]); }); it('should not allow pipes in hostListeners', () => { @Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}}) class DirectiveWithHostListener { } TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); expect(() => TestBed.createComponent(MyComp)) .toThrowError(/Cannot have a pipe in an action expression/); }); if (getDOM().supportsDOMEvents()) { it('should support preventing default on render events', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveListeningDomEventPrevent, DirectiveListeningDomEventNoPrevent] }); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const dispatchedEvent = getDOM().createMouseEvent('click'); const dispatchedEvent2 = getDOM().createMouseEvent('click'); getDOM().dispatchEvent(fixture.debugElement.children[0].nativeElement, dispatchedEvent); getDOM().dispatchEvent(fixture.debugElement.children[1].nativeElement, dispatchedEvent2); expect(getDOM().isPrevented(dispatchedEvent)).toBe(true); expect(getDOM().isPrevented(dispatchedEvent2)).toBe(false); expect(getDOM().getChecked(fixture.debugElement.children[0].nativeElement)).toBeFalsy(); expect(getDOM().getChecked(fixture.debugElement.children[1].nativeElement)).toBeTruthy(); }); } it('should support render global events from multiple directives', () => { TestBed.configureTestingModule( {declarations: [MyComp, DirectiveListeningDomEvent, DirectiveListeningDomEventOther]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const doc = TestBed.get(DOCUMENT); globalCounter = 0; fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); const tc = fixture.debugElement.children[0]; const listener = tc.injector.get(DirectiveListeningDomEvent); const listenerother = tc.injector.get(DirectiveListeningDomEventOther); dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent'); expect(listener.eventTypes).toEqual(['window_domEvent']); expect(listenerother.eventType).toEqual('other_domEvent'); expect(globalCounter).toEqual(1); fixture.componentInstance.ctxBoolProp = false; fixture.detectChanges(); dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent'); expect(globalCounter).toEqual(1); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent'); expect(globalCounter).toEqual(2); // need to destroy to release all remaining global event listeners fixture.destroy(); }); describe('ViewContainerRef', () => { beforeEach(() => { // we need a module to declarate ChildCompUsingService as an entryComponent otherwise the // factory doesn't get created @NgModule({ declarations: [MyComp, DynamicViewport, ChildCompUsingService], entryComponents: [ChildCompUsingService], schemas: [NO_ERRORS_SCHEMA], }) class MyModule { } TestBed.configureTestingModule({imports: [MyModule]}); TestBed.overrideComponent( MyComp, {add: {template: '
'}}); }); describe('.createComponent', () => { it('should allow to create a component at any bound location', async(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport); dynamicVp.create(); fixture.detectChanges(); expect(fixture.debugElement.children[0].children[1].nativeElement) .toHaveText('dynamic greet'); })); it('should allow to create multiple components at a location', async(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport); dynamicVp.create(); dynamicVp.create(); fixture.detectChanges(); expect(fixture.debugElement.children[0].children[1].nativeElement) .toHaveText('dynamic greet'); expect(fixture.debugElement.children[0].children[2].nativeElement) .toHaveText('dynamic greet'); })); it('should create a component that has been freshly compiled', () => { @Component({template: ''}) class RootComp { constructor(public vc: ViewContainerRef) {} } @NgModule({ declarations: [RootComp], providers: [{provide: 'someToken', useValue: 'someRootValue'}], }) class RootModule { } @Component({template: ''}) class MyComp { constructor(@Inject('someToken') public someToken: string) {} } @NgModule({ declarations: [MyComp], providers: [{provide: 'someToken', useValue: 'someValue'}], }) class MyModule { } const compFixture = TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp); const compiler = TestBed.get(Compiler); const myCompFactory = >compiler.compileModuleAndAllComponentsSync(MyModule) .componentFactories[0]; // Note: the ComponentFactory was created directly via the compiler, i.e. it // does not have an association to an NgModuleRef. // -> expect the providers of the module that the view container belongs to. const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory); expect(compRef.instance.someToken).toBe('someRootValue'); }); it('should create a component with the passed NgModuleRef', () => { @Component({template: ''}) class RootComp { constructor(public vc: ViewContainerRef) {} } @Component({template: ''}) class MyComp { constructor(@Inject('someToken') public someToken: string) {} } @NgModule({ declarations: [RootComp, MyComp], entryComponents: [MyComp], providers: [{provide: 'someToken', useValue: 'someRootValue'}], }) class RootModule { } @NgModule({providers: [{provide: 'someToken', useValue: 'someValue'}]}) class MyModule { } const compFixture = TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp); const compiler = TestBed.get(Compiler); const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef)); const myCompFactory = (TestBed.get(ComponentFactoryResolver)) .resolveComponentFactory(MyComp); // Note: MyComp was declared as entryComponent in the RootModule, // but we pass MyModule to the createComponent call. // -> expect the providers of MyModule! const compRef = compFixture.componentInstance.vc.createComponent( myCompFactory, undefined, undefined, undefined, myModule); expect(compRef.instance.someToken).toBe('someValue'); }); it('should create a component with the NgModuleRef of the ComponentFactoryResolver', () => { @Component({template: ''}) class RootComp { constructor(public vc: ViewContainerRef) {} } @NgModule({ declarations: [RootComp], providers: [{provide: 'someToken', useValue: 'someRootValue'}], }) class RootModule { } @Component({template: ''}) class MyComp { constructor(@Inject('someToken') public someToken: string) {} } @NgModule({ declarations: [MyComp], entryComponents: [MyComp], providers: [{provide: 'someToken', useValue: 'someValue'}], }) class MyModule { } const compFixture = TestBed.configureTestingModule({imports: [RootModule]}) .createComponent(RootComp); const compiler = TestBed.get(Compiler); const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef)); const myCompFactory = myModule.componentFactoryResolver.resolveComponentFactory(MyComp); // Note: MyComp was declared as entryComponent in MyModule, // and we don't pass an explicit ModuleRef to the createComponent call. // -> expect the providers of MyModule! const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory); expect(compRef.instance.someToken).toBe('someValue'); }); }); describe('.insert', () => { it('should throw with destroyed views', async(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport); const ref = dynamicVp.create(); fixture.detectChanges(); ref.destroy(); expect(() => { dynamicVp.insert(ref.hostView); }).toThrowError('Cannot insert a destroyed View in a ViewContainer!'); })); }); describe('.move', () => { it('should throw with destroyed views', async(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport); const ref = dynamicVp.create(); fixture.detectChanges(); ref.destroy(); expect(() => { dynamicVp.move(ref.hostView, 1); }).toThrowError('Cannot move a destroyed View in a ViewContainer!'); })); }); }); it('should support static attributes', () => { TestBed.configureTestingModule({declarations: [MyComp, NeedsAttribute]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const tc = fixture.debugElement.children[0]; const needsAttribute = tc.injector.get(NeedsAttribute); expect(needsAttribute.typeAttribute).toEqual('text'); expect(needsAttribute.staticAttribute).toEqual(''); expect(needsAttribute.fooAttribute).toBeNull(); }); it('should support custom interpolation', () => { TestBed.configureTestingModule({ declarations: [ MyComp, ComponentWithCustomInterpolationA, ComponentWithCustomInterpolationB, ComponentWithDefaultInterpolation ] }); const template = `
{{ctxProp}}
`; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'Default Interpolation'; fixture.detectChanges(); expect(fixture.nativeElement) .toHaveText( 'Default InterpolationCustom Interpolation ACustom Interpolation B (Default Interpolation)'); }); }); describe('dependency injection', () => { it('should support bindings', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveProvidingInjectable, DirectiveConsumingInjectable], schemas: [NO_ERRORS_SCHEMA], }); const template = ` `; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const comp = fixture.debugElement.children[0].children[0].references !['consuming']; expect(comp.injectable).toBeAnInstanceOf(InjectableService); }); it('should support viewProviders', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveProvidingInjectableInView, DirectiveConsumingInjectable], schemas: [NO_ERRORS_SCHEMA], }); const template = ` `; TestBed.overrideComponent(DirectiveProvidingInjectableInView, {set: {template}}); const fixture = TestBed.createComponent(DirectiveProvidingInjectableInView); const comp = fixture.debugElement.children[0].references !['consuming']; expect(comp.injectable).toBeAnInstanceOf(InjectableService); }); it('should support unbounded lookup', () => { TestBed.configureTestingModule({ declarations: [ MyComp, DirectiveProvidingInjectable, DirectiveContainingDirectiveConsumingAnInjectable, DirectiveConsumingInjectableUnbounded ], schemas: [NO_ERRORS_SCHEMA], }); const template = ` `; TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(DirectiveContainingDirectiveConsumingAnInjectable, { set: { template: ` ` } }); const fixture = TestBed.createComponent(MyComp); const comp = fixture.debugElement.children[0].children[0].references !['dir']; expect(comp.directive.injectable).toBeAnInstanceOf(InjectableService); }); it('should support the event-bus scenario', () => { TestBed.configureTestingModule({ declarations: [ MyComp, GrandParentProvidingEventBus, ParentProvidingEventBus, ChildConsumingEventBus ], schemas: [NO_ERRORS_SCHEMA], }); const template = ` `; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const gpComp = fixture.debugElement.children[0]; const parentComp = gpComp.children[0]; const childComp = parentComp.children[0]; const grandParent = gpComp.injector.get(GrandParentProvidingEventBus); const parent = parentComp.injector.get(ParentProvidingEventBus); const child = childComp.injector.get(ChildConsumingEventBus); expect(grandParent.bus.name).toEqual('grandparent'); expect(parent.bus.name).toEqual('parent'); expect(parent.grandParentBus).toBe(grandParent.bus); expect(child.bus).toBe(parent.bus); }); it('should instantiate bindings lazily', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveConsumingInjectable, ComponentProvidingLoggingInjectable], schemas: [NO_ERRORS_SCHEMA], }); const template = ` `; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const providing = fixture.debugElement.children[0].references !['providing']; expect(providing.created).toBe(false); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); expect(providing.created).toBe(true); }); }); describe('corner cases', () => { it('should remove script tags from templates', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = `
beforeinsideafter
`; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(getDOM().querySelectorAll(fixture.nativeElement, 'script').length).toEqual(0); }); it('should throw when using directives without selector', () => { @Directive({}) class SomeDirective { } @Component({selector: 'comp', template: ''}) class SomeComponent { } TestBed.configureTestingModule({declarations: [MyComp, SomeDirective, SomeComponent]}); expect(() => TestBed.createComponent(MyComp)) .toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`); }); it('should throw when using directives with empty string selector', () => { @Directive({selector: ''}) class SomeDirective { } @Component({selector: 'comp', template: ''}) class SomeComponent { } TestBed.configureTestingModule({declarations: [MyComp, SomeDirective, SomeComponent]}); expect(() => TestBed.createComponent(MyComp)) .toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`); }); it('should use a default element name for components without selectors', () => { let noSelectorComponentFactory: ComponentFactory = undefined !; @Component({template: '----'}) class NoSelectorComponent { } @Component({selector: 'some-comp', template: '', entryComponents: [NoSelectorComponent]}) class SomeComponent { constructor(componentFactoryResolver: ComponentFactoryResolver) { // grab its own component factory noSelectorComponentFactory = componentFactoryResolver.resolveComponentFactory(NoSelectorComponent) !; } } TestBed.configureTestingModule({declarations: [SomeComponent, NoSelectorComponent]}); // get the factory TestBed.createComponent(SomeComponent); expect(noSelectorComponentFactory.selector).toBe('ng-component'); expect( getDOM() .nodeName(noSelectorComponentFactory.create(Injector.NULL).location.nativeElement) .toLowerCase()) .toEqual('ng-component'); }); }); describe('error handling', () => { it('should report a meaningful error when a directive is missing annotation', () => { 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.`); }); it('should report a meaningful error when a component is missing view annotation', () => { TestBed.configureTestingModule({declarations: [MyComp, ComponentWithoutView]}); try { TestBed.createComponent(ComponentWithoutView); } catch (e) { expect(e.message).toContain( `No template specified for component ${stringify(ComponentWithoutView)}`); } }); obsoleteInIvy('DebugContext is not patched on exceptions in ivy') .it('should provide an error context when an error happens in DI', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveThrowingAnError], schemas: [NO_ERRORS_SCHEMA], }); const template = ``; TestBed.overrideComponent(MyComp, {set: {template}}); try { TestBed.createComponent(MyComp); throw 'Should throw'; } catch (e) { const c = getDebugContext(e); expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV'); expect((c.injector).get).toBeTruthy(); } }); obsoleteInIvy('DebugContext is not patched on exceptions in ivy') .it('should provide an error context when an error happens in change detection', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveThrowingAnError]}); const template = ``; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); try { fixture.detectChanges(); throw 'Should throw'; } catch (e) { const c = getDebugContext(e); expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('INPUT'); expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV'); expect((c.injector).get).toBeTruthy(); expect(c.context).toEqual(fixture.componentInstance); expect(c.references['local']).toBeDefined(); } }); obsoleteInIvy('DebugContext is not patched on exceptions in ivy') .it('should provide an error context when an error happens in change detection (text node)', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = `
{{one.two.three}}
`; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); try { fixture.detectChanges(); throw 'Should throw'; } catch (e) { const c = getDebugContext(e); expect(c.renderNode).toBeTruthy(); } }); obsoleteInIvy('DebugContext is not patched on exceptions in ivy') .it('should provide an error context when an error happens in an event handler', fakeAsync(() => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent], schemas: [NO_ERRORS_SCHEMA], }); const template = ``; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); tick(); const tc = fixture.debugElement.children[0]; const errorHandler = tc.injector.get(ErrorHandler); let err: any; spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e); tc.injector.get(DirectiveEmittingEvent).fireEvent('boom'); expect(err).toBeTruthy(); const c = getDebugContext(err); expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN'); expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV'); expect((c.injector).get).toBeTruthy(); expect(c.context).toEqual(fixture.componentInstance); expect(c.references['local']).toBeDefined(); })); }); it('should support imperative views', () => { TestBed.configureTestingModule({declarations: [MyComp, SimpleImperativeViewComponent]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); expect(fixture.nativeElement).toHaveText('hello imp view'); }); it('should support moving embedded views around', () => { TestBed.configureTestingModule({ declarations: [MyComp, SomeImperativeViewport], providers: [{provide: ANCHOR_ELEMENT, useValue: el('
')}], }); const template = '
hello
'; TestBed.overrideComponent(MyComp, {set: {template}}); const anchorElement = getTestBed().get(ANCHOR_ELEMENT); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(anchorElement).toHaveText(''); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); expect(anchorElement).toHaveText('hello'); fixture.componentInstance.ctxBoolProp = false; fixture.detectChanges(); expect(fixture.nativeElement).toHaveText(''); }); describe('Property bindings', () => { modifiedInIvy('Unknown property error thrown during update mode, not creation mode') .it('should throw on bindings to unknown properties', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); try { TestBed.createComponent(MyComp); throw 'Should throw'; } catch (e) { expect(e.message).toMatch( /Template parse errors:\nCan't bind to 'unknown' since it isn't a known property of 'div'. \("
\]unknown="{{ctxProp}}"><\/div>"\): .*MyComp.html@0:5/); } }); onlyInIvy('Unknown property error thrown during update mode, not creation mode') .it('should throw on bindings to unknown properties', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); try { const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); throw 'Should throw'; } catch (e) { expect(e.message).toMatch( /Template error: Can't bind to 'unknown' since it isn't a known property of 'div'./); } }); it('should throw on bindings to unknown properties of containers', () => { TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]}); const template = '
{{item}}
'; TestBed.overrideComponent(MyComp, {set: {template}}); try { const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); throw 'Should throw'; } catch (e) { expect(e.message).toMatch( /Can't bind to 'ngForIn' since it isn't a known property of 'div'./); } }); it('should not throw for property binding to a non-existing property when there is a matching directive property', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); expect(() => TestBed.createComponent(MyComp)).not.toThrow(); }); it('should not be created when there is a directive with the same property', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTitle]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'TITLE'; fixture.detectChanges(); const el = getDOM().querySelector(fixture.nativeElement, 'span'); expect(el.title).toBeFalsy(); }); it('should work when a directive uses hostProperty to update the DOM element', () => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTitleAndHostProperty]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'TITLE'; fixture.detectChanges(); const el = getDOM().querySelector(fixture.nativeElement, 'span'); expect(getDOM().getProperty(el, 'title')).toEqual('TITLE'); }); }); describe('logging property updates', () => { it('should reflect property values as attributes', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'hello'; fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('ng-reflect-dir-prop="hello"'); }); it('should reflect property values on unbound inputs', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('ng-reflect-dir-prop="hello"'); expect(html).not.toContain('ng-reflect-title'); }); it(`should work with prop names containing '$'`, () => { TestBed.configureTestingModule({declarations: [ParentCmp, SomeCmpWithInput]}); const fixture = TestBed.createComponent(ParentCmp); fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('ng-reflect-test_="hello"'); }); it('should reflect property values on template comments', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent( MyComp, {set: {template: ``}}) .createComponent(MyComp); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('"ng-reflect-ng-if": "true"'); }); it('should reflect property values on ng-containers', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent( MyComp, {set: {template: `content`}}) .createComponent(MyComp); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('"ng-reflect-ng-if": "true"'); }); it('should reflect property values of multiple directive bound to the same input name', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'hello'; fixture.detectChanges(); const html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('ng-reflect-dir-prop="hello"'); expect(html).toContain('ng-reflect-dir-prop2="hello"'); }); it('should indicate when toString() throws', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('[ERROR]'); }); it('should not reflect undefined values', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'hello'; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)) .toContain('ng-reflect-dir-prop="hello"'); fixture.componentInstance.ctxProp = undefined !; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)).not.toContain('ng-reflect-'); }); it('should not reflect null values', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = 'hello'; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)) .toContain('ng-reflect-dir-prop="hello"'); fixture.componentInstance.ctxProp = null !; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)).not.toContain('ng-reflect-'); }); it('should reflect empty strings', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]}); TestBed.overrideComponent( MyComp, {set: {template: `
`}}); const fixture = TestBed.createComponent(MyComp); fixture.componentInstance.ctxProp = ''; fixture.detectChanges(); expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-dir-prop=""'); }); it('should not reflect in comment nodes when the value changes to undefined', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent( MyComp, {set: {template: ``}}) .createComponent(MyComp); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); let html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('bindings={'); expect(html).toContain('"ng-reflect-ng-if": "true"'); fixture.componentInstance.ctxBoolProp = undefined !; fixture.detectChanges(); html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('bindings={'); expect(html).not.toContain('ng-reflect'); }); it('should reflect in comment nodes when the value changes to null', () => { const fixture = TestBed.configureTestingModule({declarations: [MyComp]}) .overrideComponent( MyComp, {set: {template: ``}}) .createComponent(MyComp); fixture.componentInstance.ctxBoolProp = true; fixture.detectChanges(); let html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('bindings={'); expect(html).toContain('"ng-reflect-ng-if": "true"'); fixture.componentInstance.ctxBoolProp = null !; fixture.detectChanges(); html = getDOM().getInnerHTML(fixture.nativeElement); expect(html).toContain('bindings={'); expect(html).toContain('"ng-reflect-ng-if": null'); }); }); describe('property decorators', () => { it('should support property decorators', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveWithPropDecorators], schemas: [NO_ERRORS_SCHEMA], }); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators); expect(dir.dirProp).toEqual('aaa'); }); it('should support host binding decorators', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveWithPropDecorators], schemas: [NO_ERRORS_SCHEMA], }); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators); dir.myAttr = 'aaa'; fixture.detectChanges(); expect(getDOM().getOuterHTML(fixture.debugElement.children[0].nativeElement)) .toContain('my-attr="aaa"'); }); if (getDOM().supportsDOMEvents()) { it('should support event decorators', fakeAsync(() => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveWithPropDecorators], schemas: [NO_ERRORS_SCHEMA], }); const template = ``; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); tick(); const emitter = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators); emitter.fireEvent('fired !'); tick(); expect(fixture.componentInstance.ctxProp).toEqual('called'); })); it('should support host listener decorators', () => { TestBed.configureTestingModule({ declarations: [MyComp, DirectiveWithPropDecorators], schemas: [NO_ERRORS_SCHEMA], }); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators); const native = fixture.debugElement.children[0].nativeElement; getDOM().dispatchEvent(native, getDOM().createMouseEvent('click')); expect(dir.target).toBe(native); }); } it('should support defining views in the component decorator', () => { TestBed.configureTestingModule({ declarations: [MyComp, ComponentWithTemplate], imports: [CommonModule], schemas: [NO_ERRORS_SCHEMA], }); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); fixture.detectChanges(); const native = fixture.debugElement.children[0].nativeElement; expect(native).toHaveText('No View Decorator: 123'); }); }); describe('whitespaces in templates', () => { it('should not remove whitespaces by default', async(() => { @Component({ selector: 'comp', template: 'foo bar', }) class MyCmp { } const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp); f.detectChanges(); expect(f.nativeElement.childNodes.length).toBe(2); })); it('should not remove whitespaces when explicitly requested not to do so', async(() => { @Component({ selector: 'comp', template: 'foo bar', preserveWhitespaces: true, }) class MyCmp { } const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp); f.detectChanges(); expect(f.nativeElement.childNodes.length).toBe(3); })); it('should remove whitespaces when explicitly requested to do so', async(() => { @Component({ selector: 'comp', template: 'foo bar', preserveWhitespaces: false, }) class MyCmp { } const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp); f.detectChanges(); expect(f.nativeElement.childNodes.length).toBe(2); })); }); if (getDOM().supportsDOMEvents()) { describe('svg', () => { it('should support svg elements', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = ''; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const el = fixture.nativeElement; const svg = getDOM().childNodes(el)[0]; const use = getDOM().childNodes(svg)[0]; expect(getDOM().getProperty(svg, 'namespaceURI')) .toEqual('http://www.w3.org/2000/svg'); expect(getDOM().getProperty(use, 'namespaceURI')) .toEqual('http://www.w3.org/2000/svg'); const firstAttribute = getDOM().getProperty(use, 'attributes')[0]; expect(firstAttribute.name).toEqual('xlink:href'); expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink'); }); it('should support foreignObjects with document fragments', () => { TestBed.configureTestingModule({declarations: [MyComp]}); const template = '

Test

'; TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp); const el = fixture.nativeElement; const svg = getDOM().childNodes(el)[0]; const foreignObject = getDOM().childNodes(svg)[0]; const p = getDOM().childNodes(foreignObject)[0]; expect(getDOM().getProperty(svg, 'namespaceURI')) .toEqual('http://www.w3.org/2000/svg'); expect(getDOM().getProperty(foreignObject, 'namespaceURI')) .toEqual('http://www.w3.org/2000/svg'); expect(getDOM().getProperty(p, 'namespaceURI')) .toEqual('http://www.w3.org/1999/xhtml'); }); }); describe('attributes', () => { it('should support attributes with namespace', () => { TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); const template = ''; TestBed.overrideComponent(SomeCmp, {set: {template}}); const fixture = TestBed.createComponent(SomeCmp); const useEl = getDOM().firstChild(fixture.nativeElement); expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) .toEqual('#id'); }); it('should support binding to attributes with namespace', () => { TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); const template = ''; TestBed.overrideComponent(SomeCmp, {set: {template}}); const fixture = TestBed.createComponent(SomeCmp); const cmp = fixture.componentInstance; const useEl = getDOM().firstChild(fixture.nativeElement); cmp.value = '#id'; fixture.detectChanges(); expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) .toEqual('#id'); cmp.value = null; fixture.detectChanges(); expect(getDOM().hasAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) .toEqual(false); }); }); } }); } @Component({selector: 'cmp-with-default-interpolation', template: `{{text}}`}) class ComponentWithDefaultInterpolation { text = 'Default Interpolation'; } @Component({ selector: 'cmp-with-custom-interpolation-a', template: `
{%text%}
`, interpolation: ['{%', '%}'] }) class ComponentWithCustomInterpolationA { text = 'Custom Interpolation A'; } @Component({ selector: 'cmp-with-custom-interpolation-b', template: `
{**text%}
()`, interpolation: ['{**', '%}'] }) class ComponentWithCustomInterpolationB { text = 'Custom Interpolation B'; } @Injectable() class MyService { greeting: string; constructor() { this.greeting = 'hello'; } } @Component({selector: 'simple-imp-cmp', template: ''}) class SimpleImperativeViewComponent { done: any; constructor(self: ElementRef) { const hostElement = self.nativeElement; getDOM().appendChild(hostElement, el('hello imp view')); } } @Directive({selector: 'dynamic-vp'}) class DynamicViewport { private componentFactory: ComponentFactory; private injector: Injector; constructor(private vc: ViewContainerRef, componentFactoryResolver: ComponentFactoryResolver) { const myService = new MyService(); myService.greeting = 'dynamic greet'; this.injector = Injector.create([{provide: MyService, useValue: myService}], vc.injector); this.componentFactory = componentFactoryResolver.resolveComponentFactory(ChildCompUsingService) !; } create(): ComponentRef { return this.vc.createComponent(this.componentFactory, this.vc.length, this.injector); } insert(viewRef: ViewRef, index?: number): ViewRef { return this.vc.insert(viewRef, index); } move(viewRef: ViewRef, currentIndex: number): ViewRef { return this.vc.move(viewRef, currentIndex); } } @Directive({selector: '[my-dir]', inputs: ['dirProp: elprop'], exportAs: 'mydir'}) class MyDir { dirProp: string; constructor() { this.dirProp = ''; } } @Directive({selector: '[my-dir2]', inputs: ['dirProp2: elprop'], exportAs: 'mydir2'}) class MyDir2 { dirProp2: string; constructor() { this.dirProp2 = ''; } } @Directive({selector: '[title]', inputs: ['title']}) class DirectiveWithTitle { // TODO(issue/24571): remove '!'. title !: string; } @Directive({selector: '[title]', inputs: ['title'], host: {'[title]': 'title'}}) class DirectiveWithTitleAndHostProperty { // TODO(issue/24571): remove '!'. title !: string; } @Component({selector: 'event-cmp', template: '
'}) class EventCmp { noop() {} } @Component({ selector: 'push-cmp', inputs: ['prop'], host: {'(click)': 'true'}, changeDetection: ChangeDetectionStrategy.OnPush, template: '{{field}}
' }) class PushCmp { numberOfChecks: number; prop: any; constructor() { this.numberOfChecks = 0; } noop() {} get field() { this.numberOfChecks++; return 'fixed'; } } @Component({ selector: 'push-cmp-with-ref', inputs: ['prop'], changeDetection: ChangeDetectionStrategy.OnPush, template: '{{field}}' }) class PushCmpWithRef { numberOfChecks: number; ref: ChangeDetectorRef; prop: any; constructor(ref: ChangeDetectorRef) { this.numberOfChecks = 0; this.ref = ref; } get field() { this.numberOfChecks++; return 'fixed'; } propagate() { this.ref.markForCheck(); } } @Component({ selector: 'push-cmp-with-host-event', host: {'(click)': 'ctxCallback($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: '' }) class PushCmpWithHostEvent { ctxCallback: Function = (_: any) => {}; } @Component({ selector: 'push-cmp-with-async', changeDetection: ChangeDetectionStrategy.OnPush, template: '{{field | async}}' }) class PushCmpWithAsyncPipe { numberOfChecks: number = 0; // TODO(issue/24571): remove '!'. resolve !: (result: any) => void; promise: Promise; constructor() { this.promise = new Promise((resolve) => { this.resolve = resolve; }); } get field() { this.numberOfChecks++; return this.promise; } } @Component({selector: 'my-comp', template: ''}) class MyComp { ctxProp: string; ctxNumProp: number; ctxBoolProp: boolean; ctxArrProp: number[]; toStringThrow = {toString: function() { throw 'boom'; }}; constructor() { this.ctxProp = 'initial value'; this.ctxNumProp = 0; this.ctxBoolProp = false; this.ctxArrProp = [0, 1, 2]; } throwError() { throw 'boom'; } } @Component({ selector: 'child-cmp', inputs: ['dirProp'], viewProviders: [MyService], template: '{{ctxProp}}' }) class ChildComp { ctxProp: string; dirProp: string|null; constructor(service: MyService) { this.ctxProp = service.greeting; this.dirProp = null; } } @Component({selector: 'child-cmp-no-template', template: ''}) class ChildCompNoTemplate { ctxProp: string = 'hello'; } @Component({selector: 'child-cmp-svc', template: '{{ctxProp}}'}) class ChildCompUsingService { ctxProp: string; constructor(service: MyService) { this.ctxProp = service.greeting; } } @Directive({selector: 'some-directive'}) class SomeDirective { } class SomeDirectiveMissingAnnotation {} @Component({ selector: 'cmp-with-host', template: '

Component with an injected host

', }) class CompWithHost { myHost: SomeDirective; constructor(@Host() someComp: SomeDirective) { this.myHost = someComp; } } @Component({selector: '[child-cmp2]', viewProviders: [MyService]}) class ChildComp2 { ctxProp: string; dirProp: string|null; constructor(service: MyService) { this.ctxProp = service.greeting; this.dirProp = null; } } class SomeViewportContext { constructor(public someTmpl: string) {} } @Directive({selector: '[some-viewport]'}) class SomeViewport { constructor(public container: ViewContainerRef, templateRef: TemplateRef) { container.createEmbeddedView(templateRef, new SomeViewportContext('hello')); container.createEmbeddedView(templateRef, new SomeViewportContext('again')); } } @Directive({selector: '[pollutedContext]'}) class PollutedContext { constructor(private tplRef: TemplateRef, private vcRef: ViewContainerRef) { const evRef = this.vcRef.createEmbeddedView(this.tplRef); evRef.context.bar = 'baz'; } } @Directive({selector: '[noContext]'}) class NoContext { constructor(private tplRef: TemplateRef, private vcRef: ViewContainerRef) { this.vcRef.createEmbeddedView(this.tplRef); } } @Pipe({name: 'double'}) class DoublePipe implements PipeTransform, OnDestroy { ngOnDestroy() {} transform(value: any) { return `${value}${value}`; } } @Directive({selector: '[emitter]', outputs: ['event']}) class DirectiveEmittingEvent { msg: string; event: EventEmitter; constructor() { this.msg = ''; this.event = new EventEmitter(); } fireEvent(msg: string) { this.event.emit(msg); } } @Directive({selector: '[update-host-attributes]', host: {'role': 'button'}}) class DirectiveUpdatingHostAttributes { } @Directive({selector: '[update-host-properties]', host: {'[id]': 'id'}}) class DirectiveUpdatingHostProperties { id: string; constructor() { this.id = 'one'; } } @Directive({selector: '[listener]', host: {'(event)': 'onEvent($event)'}}) class DirectiveListeningEvent { msg: string; constructor() { this.msg = ''; } onEvent(msg: string) { this.msg = msg; } } @Directive({ selector: '[listener]', host: { '(domEvent)': 'onEvent($event.type)', '(window:domEvent)': 'onWindowEvent($event.type)', '(document:domEvent)': 'onDocumentEvent($event.type)', '(body:domEvent)': 'onBodyEvent($event.type)' } }) 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); } } let globalCounter = 0; @Directive({selector: '[listenerother]', host: {'(window:domEvent)': 'onEvent($event.type)'}}) class DirectiveListeningDomEventOther { eventType: string; constructor() { this.eventType = ''; } onEvent(eventType: string) { globalCounter++; this.eventType = 'other_' + eventType; } } @Directive({selector: '[listenerprevent]', host: {'(click)': 'onEvent($event)'}}) class DirectiveListeningDomEventPrevent { onEvent(event: any) { return false; } } @Directive({selector: '[listenernoprevent]', host: {'(click)': 'onEvent($event)'}}) class DirectiveListeningDomEventNoPrevent { onEvent(event: any) { return true; } } @Directive({selector: '[id]', inputs: ['id']}) class IdDir { // TODO(issue/24571): remove '!'. id !: string; } @Directive({selector: '[customEvent]'}) class EventDir { @Output() customEvent = new EventEmitter(); doSomething() {} } @Directive({selector: '[static]'}) class NeedsAttribute { typeAttribute: string; staticAttribute: string; fooAttribute: string; constructor( @Attribute('type') typeAttribute: string, @Attribute('static') staticAttribute: string, @Attribute('foo') fooAttribute: string) { this.typeAttribute = typeAttribute; this.staticAttribute = staticAttribute; this.fooAttribute = fooAttribute; } } @Injectable() class PublicApi { } @Directive({ selector: '[public-api]', providers: [{provide: PublicApi, useExisting: PrivateImpl, deps: []}] }) class PrivateImpl extends PublicApi { } @Directive({selector: '[needs-public-api]'}) class NeedsPublicApi { constructor(@Host() api: PublicApi) { expect(api instanceof PrivateImpl).toBe(true); } } class ToolbarContext { constructor(public toolbarProp: string) {} } @Directive({selector: '[toolbarpart]'}) class ToolbarPart { templateRef: TemplateRef; constructor(templateRef: TemplateRef) { this.templateRef = templateRef; } } @Directive({selector: '[toolbarVc]', inputs: ['toolbarVc']}) class ToolbarViewContainer { constructor(public vc: ViewContainerRef) {} set toolbarVc(part: ToolbarPart) { this.vc.createEmbeddedView(part.templateRef, new ToolbarContext('From toolbar'), 0); } } @Component({ selector: 'toolbar', template: 'TOOLBAR(
)', }) class ToolbarComponent { // TODO(issue/24571): remove '!'. @ContentChildren(ToolbarPart) query !: QueryList; ctxProp: string = 'hello world'; constructor() {} } @Directive({selector: '[two-way]', inputs: ['control'], outputs: ['controlChange']}) class DirectiveWithTwoWayBinding { controlChange = new EventEmitter(); control: any = null; triggerChange(value: any) { this.controlChange.emit(value); } } @Injectable() class InjectableService { } function createInjectableWithLogging(inj: Injector) { inj.get(ComponentProvidingLoggingInjectable).created = true; return new InjectableService(); } @Component({ selector: 'component-providing-logging-injectable', providers: [{provide: InjectableService, useFactory: createInjectableWithLogging, deps: [Injector]}], template: '' }) class ComponentProvidingLoggingInjectable { created: boolean = false; } @Directive({selector: 'directive-providing-injectable', providers: [[InjectableService]]}) class DirectiveProvidingInjectable { } @Component({ selector: 'directive-providing-injectable', viewProviders: [[InjectableService]], template: '' }) class DirectiveProvidingInjectableInView { } @Component({ selector: 'directive-providing-injectable', providers: [{provide: InjectableService, useValue: 'host'}], viewProviders: [{provide: InjectableService, useValue: 'view'}], template: '' }) class DirectiveProvidingInjectableInHostAndView { } @Component({selector: 'directive-consuming-injectable', template: ''}) class DirectiveConsumingInjectable { injectable: any; constructor(@Host() @Inject(InjectableService) injectable: any) { this.injectable = injectable; } } @Component({selector: 'directive-containing-directive-consuming-an-injectable'}) class DirectiveContainingDirectiveConsumingAnInjectable { directive: any; } @Component({selector: 'directive-consuming-injectable-unbounded', template: ''}) class DirectiveConsumingInjectableUnbounded { injectable: any; constructor( injectable: InjectableService, @SkipSelf() parent: DirectiveContainingDirectiveConsumingAnInjectable) { this.injectable = injectable; parent.directive = this; } } class EventBus { parentEventBus: EventBus; name: string; constructor(parentEventBus: EventBus, name: string) { this.parentEventBus = parentEventBus; this.name = name; } } @Directive({ selector: 'grand-parent-providing-event-bus', providers: [{provide: EventBus, useValue: new EventBus(null !, 'grandparent')}] }) class GrandParentProvidingEventBus { bus: EventBus; constructor(bus: EventBus) { this.bus = bus; } } function createParentBus(peb: EventBus) { return new EventBus(peb, 'parent'); } @Component({ selector: 'parent-providing-event-bus', providers: [{provide: EventBus, useFactory: createParentBus, deps: [[EventBus, new SkipSelf()]]}], template: `` }) class ParentProvidingEventBus { bus: EventBus; grandParentBus: EventBus; constructor(bus: EventBus, @SkipSelf() grandParentBus: EventBus) { this.bus = bus; this.grandParentBus = grandParentBus; } } @Directive({selector: 'child-consuming-event-bus'}) class ChildConsumingEventBus { bus: EventBus; constructor(@SkipSelf() bus: EventBus) { this.bus = bus; } } @Directive({selector: '[someImpvp]', inputs: ['someImpvp']}) class SomeImperativeViewport { view: EmbeddedViewRef|null; anchor: any; constructor( public vc: ViewContainerRef, public templateRef: TemplateRef, @Inject(ANCHOR_ELEMENT) anchor: any) { this.view = null; this.anchor = anchor; } set someImpvp(value: boolean) { if (this.view) { this.vc.clear(); this.view = null; } if (value) { this.view = this.vc.createEmbeddedView(this.templateRef); const nodes = this.view.rootNodes; for (let i = 0; i < nodes.length; i++) { getDOM().appendChild(this.anchor, nodes[i]); } } } } @Directive({selector: '[export-dir]', exportAs: 'dir'}) class ExportDir { } @Directive({selector: '[multiple-export-as]', exportAs: 'dirX, dirY'}) export class DirectiveWithMultipleExportAsNames { } @Component({selector: 'comp'}) class ComponentWithoutView { } @Directive({selector: '[no-duplicate]'}) class DuplicateDir { constructor(elRef: ElementRef) { getDOM().setText(elRef.nativeElement, getDOM().getText(elRef.nativeElement) + 'noduplicate'); } } @Directive({selector: '[no-duplicate]'}) class OtherDuplicateDir { constructor(elRef: ElementRef) { getDOM().setText( elRef.nativeElement, getDOM().getText(elRef.nativeElement) + 'othernoduplicate'); } } @Directive({selector: 'directive-throwing-error'}) class DirectiveThrowingAnError { constructor() { throw new Error('BOOM'); } } @Component({ selector: 'component-with-template', template: `No View Decorator:
{{item}}
` }) class ComponentWithTemplate { items = [1, 2, 3]; } @Directive({selector: 'with-prop-decorators'}) class DirectiveWithPropDecorators { target: any; // TODO(issue/24571): remove '!'. @Input('elProp') dirProp !: string; @Output('elEvent') event = new EventEmitter(); // TODO(issue/24571): remove '!'. @HostBinding('attr.my-attr') myAttr !: string; @HostListener('click', ['$event.target']) onClick(target: any) { this.target = target; } fireEvent(msg: any) { this.event.emit(msg); } } @Component({selector: 'some-cmp'}) class SomeCmp { value: any; } @Component({ selector: 'parent-cmp', template: ``, }) export class ParentCmp { name: string = 'hello'; } @Component({selector: 'cmp', template: ''}) class SomeCmpWithInput { @Input() test$: any; }