UNPKG

ipsos-components

Version:

Material Design components for Angular

552 lines (412 loc) 18.9 kB
import {async, fakeAsync, tick, ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {Component, NgModule, ViewChild, ViewContainerRef} from '@angular/core'; import { ComponentPortal, PortalModule, TemplatePortal, CdkPortal } from '@angular/cdk/portal'; import { Overlay, OverlayContainer, OverlayModule, OverlayRef, OverlayConfig, PositionStrategy, ScrollStrategy, } from './index'; describe('Overlay', () => { let overlay: Overlay; let componentPortal: ComponentPortal<PizzaMsg>; let templatePortal: TemplatePortal<any>; let overlayContainerElement: HTMLElement; let overlayContainer: OverlayContainer; let viewContainerFixture: ComponentFixture<TestComponentWithTemplatePortals>; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [OverlayModule, PortalModule, OverlayTestModule] }).compileComponents(); })); beforeEach(inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => { overlay = o; overlayContainer = oc; overlayContainerElement = oc.getContainerElement(); let fixture = TestBed.createComponent(TestComponentWithTemplatePortals); fixture.detectChanges(); templatePortal = fixture.componentInstance.templatePortal; componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef); viewContainerFixture = fixture; })); afterEach(() => { overlayContainer.ngOnDestroy(); }); it('should load a component into an overlay', () => { let overlayRef = overlay.create(); overlayRef.attach(componentPortal); expect(overlayContainerElement.textContent).toContain('Pizza'); overlayRef.dispose(); expect(overlayContainerElement.childNodes.length).toBe(0); expect(overlayContainerElement.textContent).toBe(''); }); it('should load a template portal into an overlay', () => { let overlayRef = overlay.create(); overlayRef.attach(templatePortal); expect(overlayContainerElement.textContent).toContain('Cake'); overlayRef.dispose(); expect(overlayContainerElement.childNodes.length).toBe(0); expect(overlayContainerElement.textContent).toBe(''); }); it('should disable pointer events of the pane element if detached', () => { let overlayRef = overlay.create(); let paneElement = overlayRef.overlayElement; overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); expect(paneElement.childNodes.length).not.toBe(0); expect(paneElement.style.pointerEvents) .toBe('auto', 'Expected the overlay pane to enable pointerEvents when attached.'); overlayRef.detach(); expect(paneElement.childNodes.length).toBe(0); expect(paneElement.style.pointerEvents) .toBe('none', 'Expected the overlay pane to disable pointerEvents when detached.'); }); it('should open multiple overlays', () => { let pizzaOverlayRef = overlay.create(); pizzaOverlayRef.attach(componentPortal); let cakeOverlayRef = overlay.create(); cakeOverlayRef.attach(templatePortal); expect(overlayContainerElement.childNodes.length).toBe(2); expect(overlayContainerElement.textContent).toContain('Pizza'); expect(overlayContainerElement.textContent).toContain('Cake'); pizzaOverlayRef.dispose(); expect(overlayContainerElement.childNodes.length).toBe(1); expect(overlayContainerElement.textContent).toContain('Cake'); cakeOverlayRef.dispose(); expect(overlayContainerElement.childNodes.length).toBe(0); expect(overlayContainerElement.textContent).toBe(''); }); it('should ensure that the most-recently-attached overlay is on top', (() => { let pizzaOverlayRef = overlay.create(); let cakeOverlayRef = overlay.create(); pizzaOverlayRef.attach(componentPortal); cakeOverlayRef.attach(templatePortal); expect(pizzaOverlayRef.overlayElement.nextSibling) .toBeTruthy('Expected pizza to be on the bottom.'); expect(cakeOverlayRef.overlayElement.nextSibling) .toBeFalsy('Expected cake to be on top.'); pizzaOverlayRef.dispose(); cakeOverlayRef.detach(); pizzaOverlayRef = overlay.create(); pizzaOverlayRef.attach(componentPortal); cakeOverlayRef.attach(templatePortal); expect(pizzaOverlayRef.overlayElement.nextSibling) .toBeTruthy('Expected pizza to still be on the bottom.'); expect(cakeOverlayRef.overlayElement.nextSibling) .toBeFalsy('Expected cake to still be on top.'); })); it('should set the direction', () => { const config = new OverlayConfig({direction: 'rtl'}); overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.getAttribute('dir')).toEqual('rtl'); }); it('should emit when an overlay is attached', () => { let overlayRef = overlay.create(); let spy = jasmine.createSpy('attachments spy'); overlayRef.attachments().subscribe(spy); overlayRef.attach(componentPortal); expect(spy).toHaveBeenCalled(); }); it('should emit the attachment event after everything is added to the DOM', () => { let config = new OverlayConfig({hasBackdrop: true}); let overlayRef = overlay.create(config); overlayRef.attachments().subscribe(() => { expect(overlayContainerElement.querySelector('pizza')) .toBeTruthy('Expected the overlay to have been attached.'); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')) .toBeTruthy('Expected the backdrop to have been attached.'); }); overlayRef.attach(componentPortal); }); it('should emit when an overlay is detached', () => { let overlayRef = overlay.create(); let spy = jasmine.createSpy('detachments spy'); overlayRef.detachments().subscribe(spy); overlayRef.attach(componentPortal); overlayRef.detach(); expect(spy).toHaveBeenCalled(); }); it('should not emit to the detach stream if the overlay has not been attached', () => { let overlayRef = overlay.create(); let spy = jasmine.createSpy('detachments spy'); overlayRef.detachments().subscribe(spy); overlayRef.detach(); expect(spy).not.toHaveBeenCalled(); }); it('should not emit to the detach stream on dispose if the overlay was not attached', () => { let overlayRef = overlay.create(); let spy = jasmine.createSpy('detachments spy'); overlayRef.detachments().subscribe(spy); overlayRef.dispose(); expect(spy).not.toHaveBeenCalled(); }); it('should emit the detachment event after the overlay is removed from the DOM', () => { let overlayRef = overlay.create(); overlayRef.detachments().subscribe(() => { expect(overlayContainerElement.querySelector('pizza')) .toBeFalsy('Expected the overlay to have been detached.'); }); overlayRef.attach(componentPortal); overlayRef.detach(); }); it('should emit and complete the observables when an overlay is disposed', () => { let overlayRef = overlay.create(); let disposeSpy = jasmine.createSpy('dispose spy'); let attachCompleteSpy = jasmine.createSpy('attachCompleteSpy spy'); let detachCompleteSpy = jasmine.createSpy('detachCompleteSpy spy'); overlayRef.attachments().subscribe(undefined, undefined, attachCompleteSpy); overlayRef.detachments().subscribe(disposeSpy, undefined, detachCompleteSpy); overlayRef.attach(componentPortal); overlayRef.dispose(); expect(disposeSpy).toHaveBeenCalled(); expect(attachCompleteSpy).toHaveBeenCalled(); expect(detachCompleteSpy).toHaveBeenCalled(); }); it('should complete the attachment observable before the detachment one', () => { let overlayRef = overlay.create(); let callbackOrder: string[] = []; overlayRef.attachments().subscribe(undefined, undefined, () => callbackOrder.push('attach')); overlayRef.detachments().subscribe(undefined, undefined, () => callbackOrder.push('detach')); overlayRef.attach(componentPortal); overlayRef.dispose(); expect(callbackOrder).toEqual(['attach', 'detach']); }); describe('positioning', () => { let config: OverlayConfig; beforeEach(() => { config = new OverlayConfig(); }); it('should apply the positioning strategy', fakeAsync(() => { config.positionStrategy = new FakePositionStrategy(); overlay.create(config).attach(componentPortal); viewContainerFixture.detectChanges(); tick(); expect(overlayContainerElement.querySelectorAll('.fake-positioned').length).toBe(1); })); }); describe('size', () => { let config: OverlayConfig; beforeEach(() => { config = new OverlayConfig(); }); it('should apply the width set in the config', () => { config.width = 500; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.width).toEqual('500px'); }); it('should support using other units if a string width is provided', () => { config.width = '200%'; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.width).toEqual('200%'); }); it('should apply the height set in the config', () => { config.height = 500; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.height).toEqual('500px'); }); it('should support using other units if a string height is provided', () => { config.height = '100vh'; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.height).toEqual('100vh'); }); it('should apply the min width set in the config', () => { config.minWidth = 200; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.minWidth).toEqual('200px'); }); it('should apply the min height set in the config', () => { config.minHeight = 500; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.minHeight).toEqual('500px'); }); it('should apply the max width set in the config', () => { config.maxWidth = 200; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.maxWidth).toEqual('200px'); }); it('should apply the max height set in the config', () => { config.maxHeight = 500; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.maxHeight).toEqual('500px'); }); it('should support zero widths and heights', () => { config.width = 0; config.height = 0; overlay.create(config).attach(componentPortal); const pane = overlayContainerElement.children[0] as HTMLElement; expect(pane.style.width).toEqual('0px'); expect(pane.style.height).toEqual('0px'); }); }); describe('backdrop', () => { let config: OverlayConfig; beforeEach(() => { config = new OverlayConfig(); config.hasBackdrop = true; }); it('should create and destroy an overlay backdrop', () => { let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; expect(backdrop).toBeTruthy(); expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-showing'); let backdropClickHandler = jasmine.createSpy('backdropClickHander'); overlayRef.backdropClick().subscribe(backdropClickHandler); backdrop.click(); expect(backdropClickHandler).toHaveBeenCalled(); }); it('should complete the backdrop click stream once the overlay is destroyed', () => { let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let completeHandler = jasmine.createSpy('backdrop complete handler'); overlayRef.backdropClick().subscribe(undefined, undefined, completeHandler); overlayRef.dispose(); expect(completeHandler).toHaveBeenCalled(); }); it('should apply the default overlay backdrop class', () => { let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; expect(backdrop.classList).toContain('cdk-overlay-dark-backdrop'); }); it('should apply a custom overlay backdrop class', () => { config.backdropClass = 'cdk-overlay-transparent-backdrop'; let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop'); }); it('should disable the pointer events of a backdrop that is being removed', () => { let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; expect(backdrop.style.pointerEvents).toBeFalsy(); overlayRef.detach(); expect(backdrop.style.pointerEvents).toBe('none'); }); it('should insert the backdrop before the overlay pane in the DOM order', () => { let overlayRef = overlay.create(config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop'); let pane = overlayContainerElement.querySelector('.cdk-overlay-pane'); let children = Array.prototype.slice.call(overlayContainerElement.children); expect(children.indexOf(backdrop)).toBeGreaterThan(-1); expect(children.indexOf(pane)).toBeGreaterThan(-1); expect(children.indexOf(backdrop)) .toBeLessThan(children.indexOf(pane), 'Expected backdrop to be before the pane in the DOM'); }); }); describe('panelClass', () => { it('should apply a custom overlay pane class', () => { const config = new OverlayConfig({panelClass: 'custom-panel-class'}); overlay.create(config).attach(componentPortal); viewContainerFixture.detectChanges(); const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.classList).toContain('custom-panel-class'); }); it('should be able to apply multiple classes', () => { const config = new OverlayConfig({panelClass: ['custom-class-one', 'custom-class-two']}); overlay.create(config).attach(componentPortal); viewContainerFixture.detectChanges(); const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.classList).toContain('custom-class-one'); expect(pane.classList).toContain('custom-class-two'); }); }); describe('scroll strategy', () => { let fakeScrollStrategy: FakeScrollStrategy; let config: OverlayConfig; let overlayRef: OverlayRef; beforeEach(() => { fakeScrollStrategy = new FakeScrollStrategy(); config = new OverlayConfig({scrollStrategy: fakeScrollStrategy}); overlayRef = overlay.create(config); }); it('should attach the overlay ref to the scroll strategy', () => { expect(fakeScrollStrategy.overlayRef).toBe(overlayRef, 'Expected scroll strategy to have been attached to the current overlay ref.'); }); it('should enable the scroll strategy when the overlay is attached', () => { overlayRef.attach(componentPortal); expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.'); }); it('should disable the scroll strategy once the overlay is detached', () => { overlayRef.attach(componentPortal); expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.'); overlayRef.detach(); expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.'); }); it('should disable the scroll strategy when the overlay is destroyed', () => { overlayRef.dispose(); expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.'); }); }); }); /** Simple component for testing ComponentPortal. */ @Component({ selector: 'pizza', template: '<p>Pizza</p>' }) class PizzaMsg { } /** Test-bed component that contains a TempatePortal and an ElementRef. */ @Component({template: `<ng-template cdk-portal>Cake</ng-template>`}) class TestComponentWithTemplatePortals { @ViewChild(CdkPortal) templatePortal: CdkPortal; constructor(public viewContainerRef: ViewContainerRef) { } } // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_COMPONENTS = [PizzaMsg, TestComponentWithTemplatePortals]; @NgModule({ imports: [OverlayModule, PortalModule], exports: TEST_COMPONENTS, declarations: TEST_COMPONENTS, entryComponents: TEST_COMPONENTS, }) class OverlayTestModule { } class FakePositionStrategy implements PositionStrategy { element: HTMLElement; apply(): void { this.element.classList.add('fake-positioned'); } attach(overlayRef: OverlayRef) { this.element = overlayRef.overlayElement; } dispose() {} } class FakeScrollStrategy implements ScrollStrategy { isEnabled = false; overlayRef: OverlayRef; attach(overlayRef: OverlayRef) { this.overlayRef = overlayRef; } enable() { this.isEnabled = true; } disable() { this.isEnabled = false; } }