UNPKG

ipsos-components

Version:

Material Design components for Angular

521 lines (392 loc) 19.5 kB
import {inject, ComponentFixture, TestBed, async} from '@angular/core/testing'; import { NgModule, Component, ViewChild, ViewChildren, QueryList, ViewContainerRef, ComponentFactoryResolver, Optional, Injector, ApplicationRef, TemplateRef } from '@angular/core'; import {CommonModule} from '@angular/common'; import {CdkPortal, CdkPortalOutlet, PortalModule} from './portal-directives'; import {Portal, ComponentPortal, TemplatePortal} from './portal'; import {DomPortalOutlet} from './dom-portal-outlet'; describe('Portals', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [PortalModule, PortalTestModule], }); TestBed.compileComponents(); })); describe('CdkPortalOutlet', () => { let fixture: ComponentFixture<PortalTestApp>; beforeEach(() => { fixture = TestBed.createComponent(PortalTestApp); }); it('should load a component into the portal', () => { // Set the selectedHost to be a ComponentPortal. let testAppComponent = fixture.debugElement.componentInstance; testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg); fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Pizza'); }); it('should load a template into the portal', () => { let testAppComponent = fixture.debugElement.componentInstance; let hostContainer = fixture.nativeElement.querySelector('.portal-container'); let templatePortal = new TemplatePortal(testAppComponent.templateRef, null!); testAppComponent.selectedPortal = templatePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present and no context is projected expect(hostContainer.textContent).toContain('Banana'); }); it('should project template context bindings in the portal', () => { let testAppComponent = fixture.debugElement.componentInstance; let hostContainer = fixture.nativeElement.querySelector('.portal-container'); // TemplatePortal without context: let templatePortal = new TemplatePortal(testAppComponent.templateRef, null!); testAppComponent.selectedPortal = templatePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present and NO context is projected expect(hostContainer.textContent).toContain('Banana - !'); // using TemplatePortal.attach method to set context testAppComponent.selectedPortal = undefined; fixture.detectChanges(); templatePortal.attach(testAppComponent.portalOutlet, {$implicit: {status: 'rotten'}}); fixture.detectChanges(); // Expect that the content of the attached portal is present and context given via the // attach method is projected expect(hostContainer.textContent).toContain('Banana - rotten!'); // using TemplatePortal constructor to set the context templatePortal = new TemplatePortal(testAppComponent.templateRef, null!, {$implicit: {status: 'fresh'}}); testAppComponent.selectedPortal = templatePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present and context given via the // constructor is projected expect(hostContainer.textContent).toContain('Banana - fresh!'); // using TemplatePortal constructor to set the context but also calling attach method with // context, the latter should take precedence: testAppComponent.selectedPortal = undefined; fixture.detectChanges(); templatePortal.attach(testAppComponent.portalOutlet, {$implicit: {status: 'rotten'}}); fixture.detectChanges(); // Expect that the content of the attached portal is present and and context given via the // attach method is projected and get precedence over constructor context expect(hostContainer.textContent).toContain('Banana - rotten!'); }); it('should dispose the host when destroyed', () => { // Set the selectedHost to be a ComponentPortal. let testAppComponent = fixture.debugElement.componentInstance; testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg); fixture.detectChanges(); expect(testAppComponent.selectedPortal.isAttached).toBe(true); fixture.destroy(); expect(testAppComponent.selectedPortal.isAttached).toBe(false); }); it('should load a component into the portal with a given injector', () => { // Create a custom injector for the component. let chocolateInjector = new ChocolateInjector(fixture.componentInstance.injector); // Set the selectedHost to be a ComponentPortal. let testAppComponent = fixture.debugElement.componentInstance; testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg, undefined, chocolateInjector); fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Pizza'); expect(hostContainer.textContent).toContain('Chocolate'); }); it('should load a <ng-template> portal', () => { let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Set the selectedHost to be a TemplatePortal. testAppComponent.selectedPortal = testAppComponent.cakePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Cake'); }); it('should load a <ng-template> portal with the `*` sugar', () => { let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Set the selectedHost to be a TemplatePortal (with the `*` syntax). testAppComponent.selectedPortal = testAppComponent.piePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Pie'); }); it('should load a <ng-template> portal with a binding', () => { let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Set the selectedHost to be a TemplatePortal. testAppComponent.selectedPortal = testAppComponent.portalWithBinding; fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Banana'); // When updating the binding value. testAppComponent.fruit = 'Mango'; fixture.detectChanges(); // Expect the new value to be reflected in the rendered output. expect(hostContainer.textContent).toContain('Mango'); }); it('should load a <ng-template> portal with an inner template', () => { let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Set the selectedHost to be a TemplatePortal. testAppComponent.selectedPortal = testAppComponent.portalWithTemplate; fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Pineapple'); // When updating the binding value. testAppComponent.fruits = ['Mangosteen']; fixture.detectChanges(); // Expect the new value to be reflected in the rendered output. expect(hostContainer.textContent).toContain('Mangosteen'); }); it('should change the attached portal', () => { let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Set the selectedHost to be a ComponentPortal. testAppComponent.selectedPortal = testAppComponent.piePortal; fixture.detectChanges(); // Expect that the content of the attached portal is present. let hostContainer = fixture.nativeElement.querySelector('.portal-container'); expect(hostContainer.textContent).toContain('Pie'); testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg); fixture.detectChanges(); expect(hostContainer.textContent).toContain('Pizza'); }); it('should detach the portal when it is set to null', () => { let testAppComponent = fixture.debugElement.componentInstance; testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg); fixture.detectChanges(); expect(testAppComponent.portalOutlet.hasAttached()).toBe(true); expect(testAppComponent.portalOutlet.portal).toBe(testAppComponent.selectedPortal); testAppComponent.selectedPortal = null; fixture.detectChanges(); expect(testAppComponent.portalOutlet.hasAttached()).toBe(false); expect(testAppComponent.portalOutlet.portal).toBeNull(); }); it('should set the `portal` when attaching a component portal programmatically', () => { let testAppComponent = fixture.debugElement.componentInstance; let portal = new ComponentPortal(PizzaMsg); testAppComponent.portalOutlet.attachComponentPortal(portal); expect(testAppComponent.portalOutlet.portal).toBe(portal); }); it('should set the `portal` when attaching a template portal programmatically', () => { let testAppComponent = fixture.debugElement.componentInstance; fixture.detectChanges(); testAppComponent.portalOutlet.attachTemplatePortal(testAppComponent.cakePortal); expect(testAppComponent.portalOutlet.portal).toBe(testAppComponent.cakePortal); }); it('should clear the portal reference on destroy', () => { let testAppComponent = fixture.debugElement.componentInstance; testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg); fixture.detectChanges(); expect(testAppComponent.portalOutlet.portal).toBeTruthy(); fixture.destroy(); expect(testAppComponent.portalOutlet.portal).toBeNull(); }); }); describe('DomPortalOutlet', () => { let componentFactoryResolver: ComponentFactoryResolver; let someViewContainerRef: ViewContainerRef; let someInjector: Injector; let someFixture: ComponentFixture<any>; let someDomElement: HTMLElement; let host: DomPortalOutlet; let injector: Injector; let appRef: ApplicationRef; let deps = [ComponentFactoryResolver, Injector, ApplicationRef]; beforeEach(inject(deps, (dcl: ComponentFactoryResolver, i: Injector, ar: ApplicationRef) => { componentFactoryResolver = dcl; injector = i; appRef = ar; })); beforeEach(() => { someDomElement = document.createElement('div'); host = new DomPortalOutlet(someDomElement, componentFactoryResolver, appRef, injector); someFixture = TestBed.createComponent(ArbitraryViewContainerRefComponent); someViewContainerRef = someFixture.componentInstance.viewContainerRef; someInjector = someFixture.componentInstance.injector; }); it('should attach and detach a component portal', () => { let portal = new ComponentPortal(PizzaMsg, someViewContainerRef); let componentInstance: PizzaMsg = portal.attach(host).instance; expect(componentInstance instanceof PizzaMsg).toBe(true); expect(someDomElement.textContent).toContain('Pizza'); host.detach(); expect(someDomElement.innerHTML).toBe(''); }); it('should attach and detach a component portal with a given injector', () => { let fixture = TestBed.createComponent(ArbitraryViewContainerRefComponent); someViewContainerRef = fixture.componentInstance.viewContainerRef; someInjector = fixture.componentInstance.injector; let chocolateInjector = new ChocolateInjector(someInjector); let portal = new ComponentPortal(PizzaMsg, someViewContainerRef, chocolateInjector); let componentInstance: PizzaMsg = portal.attach(host).instance; fixture.detectChanges(); expect(componentInstance instanceof PizzaMsg).toBe(true); expect(someDomElement.textContent).toContain('Pizza'); expect(someDomElement.textContent).toContain('Chocolate'); host.detach(); expect(someDomElement.innerHTML).toBe(''); }); it('should attach and detach a template portal', () => { let fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); fixture.componentInstance.cakePortal.attach(host); expect(someDomElement.textContent).toContain('Cake'); }); it('should render a template portal with an inner template', () => { let fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); fixture.componentInstance.portalWithTemplate.attach(host); expect(someDomElement.textContent).toContain('Durian'); }); it('should attach and detach a template portal with a binding', () => { let fixture = TestBed.createComponent(PortalTestApp); let testAppComponent = fixture.debugElement.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. fixture.detectChanges(); // Attach the TemplatePortal. testAppComponent.portalWithBinding.attach(host, {$implicit: {status: 'fresh'}}); fixture.detectChanges(); // Now that the portal is attached, change detection has to happen again in order // for the bindings to update. fixture.detectChanges(); // Expect that the content of the attached portal is present. expect(someDomElement.textContent).toContain('Banana - fresh'); // When updating the binding value. testAppComponent.fruit = 'Mango'; fixture.detectChanges(); // Expect the new value to be reflected in the rendered output. expect(someDomElement.textContent).toContain('Mango'); host.detach(); expect(someDomElement.innerHTML).toBe(''); }); it('should change the attached portal', () => { let fixture = TestBed.createComponent(ArbitraryViewContainerRefComponent); someViewContainerRef = fixture.componentInstance.viewContainerRef; let appFixture = TestBed.createComponent(PortalTestApp); appFixture.detectChanges(); appFixture.componentInstance.piePortal.attach(host); expect(someDomElement.textContent).toContain('Pie'); host.detach(); host.attach(new ComponentPortal(PizzaMsg, someViewContainerRef)); expect(someDomElement.textContent).toContain('Pizza'); }); it('should attach and detach a component portal without a ViewContainerRef', () => { let portal = new ComponentPortal(PizzaMsg); let componentInstance: PizzaMsg = portal.attach(host).instance; expect(componentInstance instanceof PizzaMsg) .toBe(true, 'Expected a PizzaMsg component to be created'); expect(someDomElement.textContent) .toContain('Pizza', 'Expected the static string "Pizza" in the DomPortalOutlet.'); componentInstance.snack = new Chocolate(); someFixture.detectChanges(); expect(someDomElement.textContent) .toContain('Chocolate', 'Expected the bound string "Chocolate" in the DomPortalOutlet'); host.detach(); expect(someDomElement.innerHTML) .toBe('', 'Expected the DomPortalOutlet to be empty after detach'); }); it('should call the dispose function even if the host has no attached content', () => { let spy = jasmine.createSpy('host dispose spy'); expect(host.hasAttached()).toBe(false, 'Expected host not to have attached content.'); host.setDisposeFn(spy); host.dispose(); expect(spy).toHaveBeenCalled(); }); }); }); class Chocolate { toString() { return 'Chocolate'; } } class ChocolateInjector { constructor(public parentInjector: Injector) { } get(token: any) { return token === Chocolate ? new Chocolate() : this.parentInjector.get<any>(token); } } /** Simple component for testing ComponentPortal. */ @Component({ selector: 'pizza-msg', template: '<p>Pizza</p><p>{{snack}}</p>', }) class PizzaMsg { constructor(@Optional() public snack: Chocolate) { } } /** Simple component to grab an arbitrary ViewContainerRef */ @Component({ selector: 'some-placeholder', template: '<p>Hello</p>' }) class ArbitraryViewContainerRefComponent { constructor(public viewContainerRef: ViewContainerRef, public injector: Injector) { } } /** Test-bed component that contains a portal outlet and a couple of template portals. */ @Component({ selector: 'portal-test', template: ` <div class="portal-container"> <ng-template [cdkPortalOutlet]="selectedPortal"></ng-template> </div> <ng-template cdk-portal>Cake</ng-template> <div *cdk-portal>Pie</div> <ng-template cdk-portal let-data> {{fruit}} - {{ data?.status }} </ng-template> <ng-template cdk-portal> <ul> <li *ngFor="let fruitName of fruits"> {{fruitName}} </li> </ul> </ng-template> <ng-template #templateRef let-data> {{fruit}} - {{ data?.status }}!</ng-template> `, }) class PortalTestApp { @ViewChildren(CdkPortal) portals: QueryList<CdkPortal>; @ViewChild(CdkPortalOutlet) portalOutlet: CdkPortalOutlet; @ViewChild('templateRef', { read: TemplateRef }) templateRef: TemplateRef<any>; selectedPortal: Portal<any>; fruit: string = 'Banana'; fruits = ['Apple', 'Pineapple', 'Durian']; constructor(public injector: Injector) { } get cakePortal() { return this.portals.first; } get piePortal() { return this.portals.toArray()[1]; } get portalWithBinding() { return this.portals.toArray()[2]; } get portalWithTemplate() { return this.portals.toArray()[3]; } } // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_COMPONENTS = [PortalTestApp, ArbitraryViewContainerRefComponent, PizzaMsg]; @NgModule({ imports: [CommonModule, PortalModule], exports: TEST_COMPONENTS, declarations: TEST_COMPONENTS, entryComponents: TEST_COMPONENTS, }) class PortalTestModule { }