2755 lines
		
	
	
		
			106 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			2755 lines
		
	
	
		
			106 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @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, ɵgetDOM as getDOM} from '@angular/common';
 | |
| import {Compiler, ComponentFactory, ComponentRef, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, SkipSelf, ViewRef, ɵivyEnabled as ivyEnabled} from '@angular/core';
 | |
| import {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 {createMouseEvent, dispatchEvent, el, isCommentNode} from '@angular/platform-browser/testing/src/browser_util';
 | |
| import {expect} from '@angular/platform-browser/testing/src/matchers';
 | |
| import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
 | |
| 
 | |
| 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 = '<div>{{ctxProp}}</div>';
 | |
|         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 = '<div>{{null}}{{ctxProp}}</div>';
 | |
|         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 = '<div>{{null == undefined}}|{{null === undefined}}</div>';
 | |
|         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 =
 | |
|             `<div>before{{'0'}}a{{'1'}}b{{'2'}}c{{'3'}}d{{'4'}}e{{'5'}}f{{'6'}}g{{'7'}}h{{'8'}}i{{'9'}}j{{'10'}}after</div>`;
 | |
|         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 =
 | |
|                `<div>0{{null}}a{{undefined}}b{{null}}c{{undefined}}d{{null}}e{{undefined}}f{{null}}g{{undefined}}h{{null}}i{{undefined}}j{{null}}1</div>`;
 | |
|            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 = '<div [id]="ctxProp"></div>';
 | |
|         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 = '<div [attr.aria-label]="ctxProp"></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'Initial aria label';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.getAttribute('aria-label'))
 | |
|             .toEqual('Initial aria label');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'Changed aria label';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.getAttribute('aria-label'))
 | |
|             .toEqual('Changed aria label');
 | |
|       });
 | |
| 
 | |
|       it('should remove an attribute when attribute expression evaluates to null', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|         const template = '<div [attr.foo]="ctxProp"></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'bar';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.getAttribute('foo')).toEqual('bar');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = null !;
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.hasAttribute('foo')).toBeFalsy();
 | |
|       });
 | |
| 
 | |
|       it('should remove style when when style expression evaluates to null', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|         const template = '<div [style.height.px]="ctxProp"></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = '10';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.style['height']).toEqual('10px');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = null !;
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.style['height']).toEqual('');
 | |
|       });
 | |
| 
 | |
|       it('should consume binding to property names where attr name and property name do not match',
 | |
|          () => {
 | |
|            TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|            const template = '<div [tabindex]="ctxNumProp"></div>';
 | |
|            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 = '<input [readOnly]="ctxBoolProp">';
 | |
|         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 = '<div innerHtml="{{ctxProp}}"></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'Some <span>HTML</span>';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.innerHTML)
 | |
|             .toEqual('Some <span>HTML</span>');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'Some other <div>HTML</div>';
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.debugElement.children[0].nativeElement.innerHTML)
 | |
|             .toEqual('Some other <div>HTML</div>');
 | |
|       });
 | |
| 
 | |
|       modifiedInIvy('Binding to the class property directly works differently')
 | |
|           .it('should consume binding to className using class alias', () => {
 | |
|             TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|             const template = '<div class="initial" [class]="ctxProp"></div>';
 | |
|             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 = '<label [for]="ctxProp"></label>';
 | |
|         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 = '<span>' +
 | |
|             '<div my-dir [elprop]="ctxProp"></div>' +
 | |
|             '<div my-dir elprop="Hi there!"></div>' +
 | |
|             '<div my-dir elprop="Hi {{\'there!\'}}"></div>' +
 | |
|             '<div my-dir elprop="One more {{ctxProp}}"></div>' +
 | |
|             '</span>';
 | |
|         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 = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
 | |
|           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 = '<child-cmp></child-cmp>';
 | |
|         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 = '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>';
 | |
|         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 = '<p my-dir></p>';
 | |
|         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 = '<p no-duplicate></p>';
 | |
|         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 = '<p [id]="ctxProp"></p>';
 | |
|         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 = '<p (customEvent)="doNothing()"></p>';
 | |
|         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: '<p>It works!</p>'})
 | |
|         class UninitializedOutputComp {
 | |
|           @Output() customEvent !: EventEmitter<any>;
 | |
|         }
 | |
| 
 | |
|         const template =
 | |
|             '<my-uninitialized-output (customEvent)="doNothing()"></my-uninitialized-output>';
 | |
|         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 = '<div public-api><div needs-public-api></div></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
|       });
 | |
| 
 | |
|       modifiedInIvy('Comment node order changed')
 | |
|           .it('should support template directives via `<ng-template>` elements.', () => {
 | |
|             TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
 | |
|             const template =
 | |
|                 '<ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template>';
 | |
|             TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|             const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|             fixture.detectChanges();
 | |
| 
 | |
|             const childNodesOfWrapper = fixture.nativeElement.childNodes;
 | |
|             // 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 =
 | |
|             '<ng-template pollutedContext let-foo="bar">{{foo}}</ng-template><ng-template noContext let-foo="bar">{{foo}}</ng-template>';
 | |
|         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 =
 | |
|             '<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>';
 | |
|         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 `<ng-template>` elements.', () => {
 | |
|         const fixture =
 | |
|             TestBed.configureTestingModule({declarations: [MyComp]})
 | |
|                 .overrideComponent(MyComp, {set: {template: '<ng-template></ng-template>'}})
 | |
|                 .createComponent(MyComp);
 | |
| 
 | |
|         const childNodesOfWrapper = fixture.nativeElement.childNodes;
 | |
|         expect(childNodesOfWrapper.length).toBe(1);
 | |
|         expect(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 =
 | |
|             '<some-directive><toolbar><ng-template toolbarpart let-toolbarProp="toolbarProp">{{ctxProp}},{{toolbarProp}},<cmp-with-host></cmp-with-host></ng-template></toolbar></some-directive>';
 | |
|         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 = '<p><child-cmp ref-alice></child-cmp></p>';
 | |
|           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 = '<div><div export-dir #localdir="dir"></div></div>';
 | |
|           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 = '<div multiple-export-as #x="dirX" #y="dirY"></div>';
 | |
|           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 =
 | |
|                  '<ng-template [ngIf]="true">{{alice.ctxProp}}</ng-template>|{{alice.ctxProp}}|<child-cmp ref-alice></child-cmp>';
 | |
|              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 =
 | |
|               '<p><child-cmp ref-alice></child-cmp><child-cmp ref-bob></child-cmp></p>';
 | |
|           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 = '<child-cmp #alice></child-cmp>';
 | |
|           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 = '<div><div ref-alice><i>Hello</i></div></div>';
 | |
|           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: '<ng-template ref-alice></ng-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 = '<p><child-cmp ref-superAlice></child-cmp></p>';
 | |
|           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 =
 | |
|                   '<ng-template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</ng-template>';
 | |
| 
 | |
|               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 <ng-template>.
 | |
|               expect(fixture.nativeElement.childNodes[2]).toHaveText('1-hello');
 | |
|             });
 | |
|       });
 | |
| 
 | |
|       describe('OnPush components', () => {
 | |
| 
 | |
|         it('should use ChangeDetectorRef to manually request a check', () => {
 | |
|           TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
 | |
|           const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
 | |
|           TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|           const cmp = fixture.debugElement.children[0].references !['cmp'];
 | |
| 
 | |
|           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 = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
 | |
|           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 = '<push-cmp-with-host-event></push-cmp-with-host-event>';
 | |
|                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', <Event>{})).not.toThrow();
 | |
|              }));
 | |
|         }
 | |
| 
 | |
|         it('should be checked when an event is fired', () => {
 | |
|           TestBed.configureTestingModule(
 | |
|               {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
 | |
|           const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
 | |
|           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', <Event>{});
 | |
|           fixture.detectChanges();
 | |
|           fixture.detectChanges();
 | |
|           expect(cmp.numberOfChecks).toEqual(2);
 | |
| 
 | |
|           // element inside of an *ngIf
 | |
|           cmpEl.children[1].triggerEventHandler('click', <Event>{});
 | |
|           fixture.detectChanges();
 | |
|           fixture.detectChanges();
 | |
|           expect(cmp.numberOfChecks).toEqual(3);
 | |
| 
 | |
|           // element inside a nested component
 | |
|           cmpEl.children[2].children[0].triggerEventHandler('click', <Event>{});
 | |
|           fixture.detectChanges();
 | |
|           fixture.detectChanges();
 | |
|           expect(cmp.numberOfChecks).toEqual(4);
 | |
| 
 | |
|           // host element
 | |
|           cmpEl.triggerEventHandler('click', <Event>{});
 | |
|           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 = '<push-cmp-with-ref [prop]="ctxProp" #cmp></push-cmp-with-ref>';
 | |
|           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 = '<push-cmp-with-async #cmp></push-cmp-with-async>';
 | |
|              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 = `
 | |
|             <some-directive>
 | |
|               <p>
 | |
|                 <cmp-with-host #child></cmp-with-host>
 | |
|               </p>
 | |
|             </some-directive>`;
 | |
|         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 = `
 | |
|             <some-directive>
 | |
|               <p *ngIf="true">
 | |
|                 <cmp-with-host #child></cmp-with-host>
 | |
|               </p>
 | |
|             </some-directive>`;
 | |
|         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 = '<div emitter listener></div>';
 | |
|            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:
 | |
|                            '<ng-template emitter listener (event)="ctxProp=$event"></ng-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 = '<div [(control)]="ctxProp" two-way></div>';
 | |
|            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 = '<div listener></div>';
 | |
|         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 = '<div listener></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
|         const doc = TestBed.inject(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(fixture.debugElement.nativeElement.getAttribute('role')).toEqual('button');
 | |
|       });
 | |
| 
 | |
|       it('should support updating host element via hostAttributes on host elements', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp, DirectiveUpdatingHostAttributes]});
 | |
|         const template = '<div update-host-attributes></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.debugElement.children[0].nativeElement.getAttribute('role'))
 | |
|             .toEqual('button');
 | |
|       });
 | |
| 
 | |
|       it('should support updating host element via hostProperties', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp, DirectiveUpdatingHostProperties]});
 | |
|         const template = '<div update-host-properties></div>';
 | |
|         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: `<div *ngFor="let id of ['forId']" host-properties></div>`}})
 | |
|                 .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 = '<div host-properties></div>';
 | |
|         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: `<div *ngFor="let id of ['forId']" host-listener></div>`}})
 | |
|                 .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 = '<div host-listener></div>';
 | |
|         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 =
 | |
|               '<input type="checkbox" listenerprevent><input type="checkbox" listenernoprevent>';
 | |
|           TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|           const dispatchedEvent = createMouseEvent('click');
 | |
|           const dispatchedEvent2 = createMouseEvent('click');
 | |
|           getDOM().dispatchEvent(fixture.debugElement.children[0].nativeElement, dispatchedEvent);
 | |
|           getDOM().dispatchEvent(fixture.debugElement.children[1].nativeElement, dispatchedEvent2);
 | |
|           expect(isPrevented(dispatchedEvent)).toBe(true);
 | |
|           expect(isPrevented(dispatchedEvent2)).toBe(false);
 | |
|           expect(fixture.debugElement.children[0].nativeElement.checked).toBeFalsy();
 | |
|           expect(fixture.debugElement.children[1].nativeElement.checked).toBeTruthy();
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       it('should support render global events from multiple directives', () => {
 | |
|         TestBed.configureTestingModule(
 | |
|             {declarations: [MyComp, DirectiveListeningDomEvent, DirectiveListeningDomEventOther]});
 | |
|         const template = '<div *ngIf="ctxBoolProp" listener listenerother></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
|         const doc = TestBed.inject(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: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
 | |
|         });
 | |
| 
 | |
|         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.inject(Compiler);
 | |
|             const myCompFactory =
 | |
|                 <ComponentFactory<MyComp>>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.inject(Compiler);
 | |
|             const myModule =
 | |
|                 compiler.compileModuleSync(MyModule).create(TestBed.inject(NgModuleRef).injector);
 | |
|             const myCompFactory =
 | |
|                 TestBed.inject(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.inject(Compiler);
 | |
|                const myModule = compiler.compileModuleSync(MyModule).create(
 | |
|                    TestBed.inject(NgModuleRef).injector);
 | |
|                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 = '<input static type="text" title>';
 | |
|         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 = `<div>{{ctxProp}}</div>
 | |
| <cmp-with-custom-interpolation-a></cmp-with-custom-interpolation-a>
 | |
| <cmp-with-custom-interpolation-b></cmp-with-custom-interpolation-b>`;
 | |
|         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 = `
 | |
|             <directive-providing-injectable >
 | |
|               <directive-consuming-injectable #consuming>
 | |
|               </directive-consuming-injectable>
 | |
|             </directive-providing-injectable>
 | |
|           `;
 | |
|         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 = `
 | |
|               <directive-consuming-injectable #consuming>
 | |
|               </directive-consuming-injectable>
 | |
|           `;
 | |
|         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 = `
 | |
|             <directive-providing-injectable>
 | |
|               <directive-containing-directive-consuming-an-injectable #dir>
 | |
|               </directive-containing-directive-consuming-an-injectable>
 | |
|             </directive-providing-injectable>
 | |
|           `;
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         TestBed.overrideComponent(DirectiveContainingDirectiveConsumingAnInjectable, {
 | |
|           set: {
 | |
|             template: `
 | |
|             <directive-consuming-injectable-unbounded></directive-consuming-injectable-unbounded>
 | |
|           `
 | |
|           }
 | |
|         });
 | |
|         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 = `
 | |
|             <grand-parent-providing-event-bus>
 | |
|               <parent-providing-event-bus>
 | |
|                 <child-consuming-event-bus>
 | |
|                 </child-consuming-event-bus>
 | |
|               </parent-providing-event-bus>
 | |
|             </grand-parent-providing-event-bus>
 | |
|           `;
 | |
|         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 = `
 | |
|               <component-providing-logging-injectable #providing>
 | |
|                 <directive-consuming-injectable *ngIf="ctxBoolProp">
 | |
|                 </directive-consuming-injectable>
 | |
|               </component-providing-logging-injectable>
 | |
|           `;
 | |
|         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 = `
 | |
|             <script>alert("Ooops");</script>
 | |
|             <div>before<script>alert("Ooops");</script><span>inside</span>after</div>`;
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         expect(fixture.nativeElement.querySelectorAll('script').length).toEqual(0);
 | |
|       });
 | |
| 
 | |
|       it('should throw when using directives without selector in NgModule declarations', () => {
 | |
|         @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 not throw when using directives without selector as base class not in declarations',
 | |
|          () => {
 | |
|            @Directive({})
 | |
|            abstract class Base {
 | |
|              constructor(readonly injector: Injector) {}
 | |
|            }
 | |
| 
 | |
|            @Directive()
 | |
|            abstract class EmptyDir {
 | |
|            }
 | |
| 
 | |
|            @Directive({inputs: ['a', 'b']})
 | |
|            class TestDirWithInputs {
 | |
|            }
 | |
| 
 | |
|            @Component({selector: 'comp', template: ''})
 | |
|            class SomeComponent extends Base {
 | |
|            }
 | |
| 
 | |
|            @Component({selector: 'comp2', template: ''})
 | |
|            class SomeComponent2 extends EmptyDir {
 | |
|            }
 | |
| 
 | |
|            @Component({selector: 'comp3', template: ''})
 | |
|            class SomeComponent3 extends TestDirWithInputs {
 | |
|            }
 | |
| 
 | |
|            TestBed.configureTestingModule(
 | |
|                {declarations: [MyComp, SomeComponent, SomeComponent2, SomeComponent3]});
 | |
|            expect(() => TestBed.createComponent(MyComp)).not.toThrowError();
 | |
|          });
 | |
| 
 | |
|       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<SomeComponent> = 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(noSelectorComponentFactory.create(Injector.NULL)
 | |
|                    .location.nativeElement.nodeName.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 = `<directive-throwing-error></directive-throwing-error>`;
 | |
|             TestBed.overrideComponent(MyComp, {set: {template}});
 | |
| 
 | |
|             try {
 | |
|               TestBed.createComponent(MyComp);
 | |
|               throw 'Should throw';
 | |
|             } catch (e) {
 | |
|               const c = getDebugContext(e);
 | |
|               expect(c.componentRenderElement.nodeName.toUpperCase()).toEqual('DIV');
 | |
|               expect((<Injector>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 = `<input [value]="one.two.three" #local>`;
 | |
|             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.nodeName.toUpperCase()).toEqual('INPUT');
 | |
|               expect(c.componentRenderElement.nodeName.toUpperCase()).toEqual('DIV');
 | |
|               expect((<Injector>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 = `<div>{{one.two.three}}</div>`;
 | |
|                 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 = `<span emitter listener (event)="throwError()" #local></span>`;
 | |
|                 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(c.renderNode.nodeName.toUpperCase()).toEqual('SPAN');
 | |
|                 expect(c.componentRenderElement.nodeName.toUpperCase()).toEqual('DIV');
 | |
|                 expect((<Injector>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 = '<simple-imp-cmp></simple-imp-cmp>';
 | |
|       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('<div></div>')}],
 | |
|       });
 | |
|       const template = '<div><div *someImpvp="ctxBoolProp">hello</div></div>';
 | |
|       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 throws an error instead of logging a warning')
 | |
|           .it('should throw on bindings to unknown properties', () => {
 | |
|             TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|             const template = '<div unknown="{{ctxProp}}"></div>';
 | |
|             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'. \("<div \[ERROR ->\]unknown="{{ctxProp}}"><\/div>"\): .*MyComp.html@0:5/);
 | |
|             }
 | |
|           });
 | |
| 
 | |
|       onlyInIvy('Unknown property warning logged instead of throwing an error')
 | |
|           .it('should throw on bindings to unknown properties', () => {
 | |
|             TestBed.configureTestingModule({declarations: [MyComp]});
 | |
|             const template = '<div unknown="{{ctxProp}}"></div>';
 | |
|             TestBed.overrideComponent(MyComp, {set: {template}});
 | |
| 
 | |
|             const spy = spyOn(console, 'warn');
 | |
|             const fixture = TestBed.createComponent(MyComp);
 | |
|             fixture.detectChanges();
 | |
|             expect(spy.calls.mostRecent().args[0])
 | |
|                 .toMatch(/Can't bind to 'unknown' since it isn't a known property of 'div'./);
 | |
|           });
 | |
| 
 | |
|       modifiedInIvy('Unknown property error thrown instead of a warning')
 | |
|           .it('should throw on bindings to unknown properties', () => {
 | |
|             TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
 | |
|             const template = '<div *ngFor="let item in ctxArrProp">{{item}}</div>';
 | |
|             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'./);
 | |
|             }
 | |
|           });
 | |
| 
 | |
|       onlyInIvy('Unknown property logs a warning instead of throwing an error')
 | |
|           .it('should throw on bindings to unknown properties', () => {
 | |
|             TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
 | |
|             const template = '<div *ngFor="let item in ctxArrProp">{{item}}</div>';
 | |
|             TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|             const spy = spyOn(console, 'warn');
 | |
|             const fixture = TestBed.createComponent(MyComp);
 | |
|             fixture.detectChanges();
 | |
|             expect(spy.calls.mostRecent().args[0])
 | |
|                 .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 = '<div my-dir [elprop]="ctxProp"></div>';
 | |
|            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 = '<span [title]="ctxProp"></span>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'TITLE';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const el = fixture.nativeElement.querySelector('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 = '<span [title]="ctxProp"></span>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'TITLE';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const el = fixture.nativeElement.querySelector('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: `<div my-dir [elprop]="ctxProp"></div>`}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'hello';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const html = fixture.nativeElement.innerHTML;
 | |
|         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: `<div my-dir elprop="hello" title="Reflect test"></div>`}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const html = fixture.nativeElement.innerHTML;
 | |
|         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 = fixture.nativeElement.innerHTML;
 | |
|         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: `<ng-template [ngIf]="ctxBoolProp"></ng-template>`}})
 | |
|                 .createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = true;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const html = fixture.nativeElement.innerHTML;
 | |
|         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: `<ng-container *ngIf="ctxBoolProp">content</ng-container>`}})
 | |
|                 .createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = true;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         const html = fixture.nativeElement.innerHTML;
 | |
|         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: `<div my-dir my-dir2 [elprop]="ctxProp"></div>`}});
 | |
|            const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|            fixture.componentInstance.ctxProp = 'hello';
 | |
|            fixture.detectChanges();
 | |
| 
 | |
|            const html = fixture.nativeElement.innerHTML;
 | |
|            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 = '<div my-dir [elprop]="toStringThrow"></div>';
 | |
|         TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.detectChanges();
 | |
|         expect(fixture.nativeElement.innerHTML).toContain('[ERROR]');
 | |
|       });
 | |
| 
 | |
|       it('should not reflect undefined values', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
 | |
|         TestBed.overrideComponent(
 | |
|             MyComp, {set: {template: `<div my-dir [elprop]="ctxProp"></div>`}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'hello';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = undefined !;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
 | |
|       });
 | |
| 
 | |
|       it('should not reflect null values', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
 | |
|         TestBed.overrideComponent(
 | |
|             MyComp, {set: {template: `<div my-dir [elprop]="ctxProp"></div>`}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = 'hello';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = null !;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
 | |
|       });
 | |
| 
 | |
|       it('should reflect empty strings', () => {
 | |
|         TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
 | |
|         TestBed.overrideComponent(
 | |
|             MyComp, {set: {template: `<div my-dir [elprop]="ctxProp"></div>`}});
 | |
|         const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxProp = '';
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         expect(fixture.nativeElement.innerHTML).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: `<ng-template [ngIf]="ctxBoolProp"></ng-template>`}})
 | |
|                 .createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = true;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         let html = fixture.nativeElement.innerHTML;
 | |
|         expect(html).toContain('bindings={');
 | |
|         expect(html).toContain('"ng-reflect-ng-if": "true"');
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = undefined !;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         html = fixture.nativeElement.innerHTML;
 | |
|         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: `<ng-template [ngIf]="ctxBoolProp"></ng-template>`}})
 | |
|                 .createComponent(MyComp);
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = true;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         let html = fixture.nativeElement.innerHTML;
 | |
|         expect(html).toContain('bindings={');
 | |
|         expect(html).toContain('"ng-reflect-ng-if": "true"');
 | |
| 
 | |
|         fixture.componentInstance.ctxBoolProp = null !;
 | |
|         fixture.detectChanges();
 | |
| 
 | |
|         html = fixture.nativeElement.innerHTML;
 | |
|         expect(html).toContain('bindings={');
 | |
|         expect(html).toContain('"ng-reflect-ng-if": null');
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
|     describe('property decorators', () => {
 | |
|       it('should support property decorators', () => {
 | |
|         TestBed.configureTestingModule({
 | |
|           declarations: [MyComp, DirectiveWithPropDecorators],
 | |
|           schemas: [NO_ERRORS_SCHEMA],
 | |
|         });
 | |
|         const template = '<with-prop-decorators elProp="aaa"></with-prop-decorators>';
 | |
|         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 = '<with-prop-decorators></with-prop-decorators>';
 | |
|         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(fixture.debugElement.children[0].nativeElement.outerHTML).toContain('my-attr="aaa"');
 | |
|       });
 | |
| 
 | |
|       if (getDOM().supportsDOMEvents()) {
 | |
|         it('should support event decorators', fakeAsync(() => {
 | |
|              TestBed.configureTestingModule({
 | |
|                declarations: [MyComp, DirectiveWithPropDecorators],
 | |
|                schemas: [NO_ERRORS_SCHEMA],
 | |
|              });
 | |
|              const template = `<with-prop-decorators (elEvent)="ctxProp='called'">`;
 | |
|              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 = '<with-prop-decorators></with-prop-decorators>';
 | |
|           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, 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 = '<component-with-template></component-with-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: '<span>foo</span>  <span>bar</span>',
 | |
|            })
 | |
|            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: '<span>foo</span>  <span>bar</span>',
 | |
|              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: '<span>foo</span>  <span>bar</span>',
 | |
|              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 = '<svg><use xlink:href="Port" /></svg>';
 | |
|           TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|           const el = fixture.nativeElement;
 | |
|           const svg = el.childNodes[0];
 | |
|           const use = svg.childNodes[0];
 | |
|           expect(getDOM().getProperty(<Element>svg, 'namespaceURI'))
 | |
|               .toEqual('http://www.w3.org/2000/svg');
 | |
|           expect(getDOM().getProperty(<Element>use, 'namespaceURI'))
 | |
|               .toEqual('http://www.w3.org/2000/svg');
 | |
| 
 | |
|           const firstAttribute = getDOM().getProperty(<Element>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 =
 | |
|               '<svg><foreignObject><xhtml:div><p>Test</p></xhtml:div></foreignObject></svg>';
 | |
|           TestBed.overrideComponent(MyComp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(MyComp);
 | |
| 
 | |
|           const el = fixture.nativeElement;
 | |
|           const svg = el.childNodes[0];
 | |
|           const foreignObject = svg.childNodes[0];
 | |
|           const p = foreignObject.childNodes[0];
 | |
|           expect(getDOM().getProperty(<Element>svg, 'namespaceURI'))
 | |
|               .toEqual('http://www.w3.org/2000/svg');
 | |
|           expect(getDOM().getProperty(<Element>foreignObject, 'namespaceURI'))
 | |
|               .toEqual('http://www.w3.org/2000/svg');
 | |
|           expect(getDOM().getProperty(<Element>p, 'namespaceURI'))
 | |
|               .toEqual('http://www.w3.org/1999/xhtml');
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe('attributes', () => {
 | |
| 
 | |
|         it('should support attributes with namespace', () => {
 | |
|           TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
 | |
|           const template = '<svg:use xlink:href="#id" />';
 | |
|           TestBed.overrideComponent(SomeCmp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(SomeCmp);
 | |
| 
 | |
|           const useEl = fixture.nativeElement.firstChild;
 | |
|           expect(useEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toEqual('#id');
 | |
|         });
 | |
| 
 | |
|         it('should support binding to attributes with namespace', () => {
 | |
|           TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
 | |
|           const template = '<svg:use [attr.xlink:href]="value" />';
 | |
|           TestBed.overrideComponent(SomeCmp, {set: {template}});
 | |
|           const fixture = TestBed.createComponent(SomeCmp);
 | |
| 
 | |
|           const cmp = fixture.componentInstance;
 | |
|           const useEl = fixture.nativeElement.firstChild;
 | |
| 
 | |
|           cmp.value = '#id';
 | |
|           fixture.detectChanges();
 | |
| 
 | |
|           expect(useEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toEqual('#id');
 | |
| 
 | |
|           cmp.value = null;
 | |
|           fixture.detectChanges();
 | |
| 
 | |
|           expect(useEl.hasAttributeNS('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: `<div>{%text%}</div>`,
 | |
|   interpolation: ['{%', '%}']
 | |
| })
 | |
| class ComponentWithCustomInterpolationA {
 | |
|   text = 'Custom Interpolation A';
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'cmp-with-custom-interpolation-b',
 | |
|   template:
 | |
|       `<div>{**text%}</div> (<cmp-with-default-interpolation></cmp-with-default-interpolation>)`,
 | |
|   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;
 | |
|     hostElement.appendChild(el('hello imp view'));
 | |
|   }
 | |
| }
 | |
| 
 | |
| @Directive({selector: 'dynamic-vp'})
 | |
| class DynamicViewport {
 | |
|   private componentFactory: ComponentFactory<ChildCompUsingService>;
 | |
|   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<ChildCompUsingService> {
 | |
|     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: '<div (click)="noop()"></div>'})
 | |
| class EventCmp {
 | |
|   noop() {}
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'push-cmp',
 | |
|   inputs: ['prop'],
 | |
|   host: {'(click)': 'true'},
 | |
|   changeDetection: ChangeDetectionStrategy.OnPush,
 | |
|   template:
 | |
|       '{{field}}<div (click)="noop()"></div><div *ngIf="true" (click)="noop()"></div><event-cmp></event-cmp>'
 | |
| })
 | |
| 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<any>;
 | |
| 
 | |
|   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: '<p>Component with an injected host</p>',
 | |
| })
 | |
| 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<SomeViewportContext>) {
 | |
|     container.createEmbeddedView(templateRef, new SomeViewportContext('hello'));
 | |
|     container.createEmbeddedView(templateRef, new SomeViewportContext('again'));
 | |
|   }
 | |
| }
 | |
| 
 | |
| @Directive({selector: '[pollutedContext]'})
 | |
| class PollutedContext {
 | |
|   constructor(private tplRef: TemplateRef<any>, private vcRef: ViewContainerRef) {
 | |
|     const evRef = this.vcRef.createEmbeddedView(this.tplRef);
 | |
|     evRef.context.bar = 'baz';
 | |
|   }
 | |
| }
 | |
| 
 | |
| @Directive({selector: '[noContext]'})
 | |
| class NoContext {
 | |
|   constructor(private tplRef: TemplateRef<any>, 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<any>;
 | |
| 
 | |
|   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<ToolbarContext>;
 | |
|   constructor(templateRef: TemplateRef<ToolbarContext>) { 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(<div *ngFor="let  part of query" [toolbarVc]="part"></div>)',
 | |
| })
 | |
| class ToolbarComponent {
 | |
|   // TODO(issue/24571): remove '!'.
 | |
|   @ContentChildren(ToolbarPart) query !: QueryList<ToolbarPart>;
 | |
|   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: `<child-consuming-event-bus></child-consuming-event-bus>`
 | |
| })
 | |
| 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<Object>|null;
 | |
|   anchor: any;
 | |
|   constructor(
 | |
|       public vc: ViewContainerRef, public templateRef: TemplateRef<Object>,
 | |
|       @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++) {
 | |
|         this.anchor.appendChild(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) { elRef.nativeElement.textContent += 'noduplicate'; }
 | |
| }
 | |
| 
 | |
| @Directive({selector: '[no-duplicate]'})
 | |
| class OtherDuplicateDir {
 | |
|   constructor(elRef: ElementRef) { elRef.nativeElement.textContent += 'othernoduplicate'; }
 | |
| }
 | |
| 
 | |
| @Directive({selector: 'directive-throwing-error'})
 | |
| class DirectiveThrowingAnError {
 | |
|   constructor() { throw new Error('BOOM'); }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'component-with-template',
 | |
|   template: `No View Decorator: <div *ngFor="let item of items">{{item}}</div>`
 | |
| })
 | |
| 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: `<cmp [test$]="name"></cmp>`,
 | |
| })
 | |
| export class ParentCmp {
 | |
|   name: string = 'hello';
 | |
| }
 | |
| 
 | |
| @Component({selector: 'cmp', template: ''})
 | |
| class SomeCmpWithInput {
 | |
|   @Input() test$: any;
 | |
| }
 | |
| 
 | |
| function isPrevented(evt: Event): boolean {
 | |
|   return evt.defaultPrevented || evt.returnValue != null && !evt.returnValue;
 | |
| }
 |