import {TestBed, ComponentFixture, inject} from '@angular/core/testing'; import {createGenericTestComponent} from '../test/common'; import {By} from '@angular/platform-browser'; import {Component, ViewChild, ChangeDetectionStrategy, Injectable, OnDestroy} from '@angular/core'; import {NgbPopoverModule} from './popover.module'; import {NgbPopoverWindow, NgbPopover} from './popover'; import {NgbPopoverConfig} from './popover-config'; @Injectable() class SpyService { called = false; } const createTestComponent = (html: string) => createGenericTestComponent(html, TestComponent) as ComponentFixture; const createOnPushTestComponent = (html: string) => >createGenericTestComponent(html, TestOnPushComponent); describe('ngb-popover-window', () => { beforeEach(() => { TestBed.configureTestingModule({declarations: [TestComponent], imports: [NgbPopoverModule.forRoot()]}); }); it('should render popover on top by default', () => { const fixture = TestBed.createComponent(NgbPopoverWindow); fixture.componentInstance.title = 'Test title'; fixture.detectChanges(); expect(fixture.nativeElement).toHaveCssClass('popover'); expect(fixture.nativeElement).toHaveCssClass('bs-popover-top'); expect(fixture.nativeElement.getAttribute('role')).toBe('tooltip'); expect(fixture.nativeElement.querySelector('.popover-header').textContent).toBe('Test title'); }); it('should position popovers as requested', () => { const fixture = TestBed.createComponent(NgbPopoverWindow); fixture.componentInstance.placement = 'left'; fixture.detectChanges(); expect(fixture.nativeElement).toHaveCssClass('bs-popover-left'); }); }); describe('ngb-popover', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestComponent, TestOnPushComponent, DestroyableCmpt], imports: [NgbPopoverModule.forRoot()], providers: [SpyService] }); }); function getWindow(element) { return element.querySelector('ngb-popover-window'); } describe('basic functionality', () => { it('should open and close a popover - default settings and content as string', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass('bs-popover-top'); expect(windowEl.textContent.trim()).toBe('TitleGreat tip!'); expect(windowEl.getAttribute('role')).toBe('tooltip'); expect(windowEl.getAttribute('id')).toBe('ngb-popover-0'); expect(windowEl.parentNode).toBe(fixture.nativeElement); expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('ngb-popover-0'); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull(); }); it('should open and close a popover - default settings and content from a template', () => { const fixture = createTestComponent(` Hello, {{name}}!
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); const defaultConfig = new NgbPopoverConfig(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass(`bs-popover-${defaultConfig.placement}`); expect(windowEl.textContent.trim()).toBe('TitleHello, World!'); expect(windowEl.getAttribute('role')).toBe('tooltip'); expect(windowEl.getAttribute('id')).toBe('ngb-popover-1'); expect(windowEl.parentNode).toBe(fixture.nativeElement); expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('ngb-popover-1'); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull(); }); it('should open and close a popover - default settings, content from a template and context supplied', () => { const fixture = createTestComponent(` Hello, {{name}}!
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); const defaultConfig = new NgbPopoverConfig(); directive.context.popover.open({name: 'John'}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass(`bs-popover-${defaultConfig.placement}`); expect(windowEl.textContent.trim()).toBe('TitleHello, John!'); expect(windowEl.getAttribute('role')).toBe('tooltip'); expect(windowEl.getAttribute('id')).toBe('ngb-popover-2'); expect(windowEl.parentNode).toBe(fixture.nativeElement); expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('ngb-popover-2'); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull(); }); it('should properly destroy TemplateRef content', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); const spyService = fixture.debugElement.injector.get(SpyService); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); expect(spyService.called).toBeFalsy(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(spyService.called).toBeTruthy(); }); it('should not open a popover if content and title are empty', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toBeNull(); }); it('should not open a popover if [disablePopover] flag', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toBeNull(); }); it('should close the popover if content and title become empty', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); fixture.componentInstance.name = ''; fixture.componentInstance.title = ''; fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should open the popover if content is empty but title has value', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).not.toBeNull(); }); it('should not close the popover if content becomes empty but title has value', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); fixture.componentInstance.name = ''; fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); }); it('should allow re-opening previously closed popovers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); }); it('should not leave dangling popovers in the DOM', () => { const fixture = createTestComponent( `
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); fixture.componentInstance.show = false; fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should properly cleanup popovers with manual triggers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); fixture.componentInstance.show = false; fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); }); describe('positioning', () => { it('should use requested position', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass('bs-popover-left'); expect(windowEl.textContent.trim()).toBe('Great tip!'); }); it('should properly position popovers when a component is using the OnPush strategy', () => { const fixture = createOnPushTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass('bs-popover-left'); expect(windowEl.textContent.trim()).toBe('Great tip!'); }); it('should have proper arrow placement', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass('bs-popover-right'); expect(windowEl).toHaveCssClass('bs-popover-right-top'); expect(windowEl.textContent.trim()).toBe('Great tip!'); }); it('should accept placement in array(second value of the array should be applied)', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); expect(windowEl).toHaveCssClass('bs-popover-top'); expect(windowEl).toHaveCssClass('bs-popover-top-right'); expect(windowEl.textContent.trim()).toBe('Great tip!'); }); it('should apply auto placement', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); const windowEl = getWindow(fixture.nativeElement); expect(windowEl).toHaveCssClass('popover'); // actual placement with auto is not known in advance, so use regex to check it expect(windowEl.getAttribute('class')).toMatch('bs-popover-\.'); expect(windowEl.textContent.trim()).toBe('Great tip!'); }); }); describe('container', () => { it('should be appended to the element matching the selector passed to "container"', () => { const selector = 'body'; const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(getWindow(window.document.querySelector(selector))).not.toBeNull(); }); it('should properly destroy popovers when the "container" option is used', () => { const selector = 'body'; const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(document.querySelector(selector))).not.toBeNull(); fixture.componentRef.instance.show = false; fixture.detectChanges(); expect(getWindow(document.querySelector(selector))).toBeNull(); }); }); describe('visibility', () => { it('should emit events when showing and hiding popover', () => { const fixture = createTestComponent( `
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); let shownSpy = spyOn(fixture.componentInstance, 'shown'); let hiddenSpy = spyOn(fixture.componentInstance, 'hidden'); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); expect(shownSpy).toHaveBeenCalled(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(hiddenSpy).toHaveBeenCalled(); }); it('should not emit close event when already closed', () => { const fixture = createTestComponent( `
`); let shownSpy = spyOn(fixture.componentInstance, 'shown'); let hiddenSpy = spyOn(fixture.componentInstance, 'hidden'); fixture.componentInstance.popover.open(); fixture.detectChanges(); fixture.componentInstance.popover.open(); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); expect(shownSpy).toHaveBeenCalled(); expect(shownSpy.calls.count()).toEqual(1); expect(hiddenSpy).not.toHaveBeenCalled(); }); it('should not emit open event when already opened', () => { const fixture = createTestComponent( `
`); let shownSpy = spyOn(fixture.componentInstance, 'shown'); let hiddenSpy = spyOn(fixture.componentInstance, 'hidden'); fixture.componentInstance.popover.close(); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); expect(shownSpy).not.toHaveBeenCalled(); expect(hiddenSpy).not.toHaveBeenCalled(); }); it('should report correct visibility', () => { const fixture = createTestComponent(`
`); fixture.detectChanges(); expect(fixture.componentInstance.popover.isOpen()).toBeFalsy(); fixture.componentInstance.popover.open(); fixture.detectChanges(); expect(fixture.componentInstance.popover.isOpen()).toBeTruthy(); fixture.componentInstance.popover.close(); fixture.detectChanges(); expect(fixture.componentInstance.popover.isOpen()).toBeFalsy(); }); }); describe('triggers', () => { beforeEach(() => { TestBed.configureTestingModule({declarations: [TestComponent], imports: [NgbPopoverModule.forRoot()]}); }); it('should support toggle triggers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should non-default toggle triggers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should support multiple triggers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); directive.triggerEventHandler('click', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should not use default for manual triggers', () => { const fixture = createTestComponent(`
`); const directive = fixture.debugElement.query(By.directive(NgbPopover)); directive.triggerEventHandler('mouseenter', {}); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should allow toggling for manual triggers', () => { const fixture = createTestComponent(`
`); const button = fixture.nativeElement.querySelector('button'); button.click(); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); button.click(); fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should allow open / close for manual triggers', () => { const fixture = createTestComponent(`
`); const buttons = fixture.nativeElement.querySelectorAll('button'); buttons[0].click(); // open fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); buttons[1].click(); // close fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); it('should not throw when open called for manual triggers and open popover', () => { const fixture = createTestComponent(`
`); const button = fixture.nativeElement.querySelector('button'); button.click(); // open fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); button.click(); // open fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).not.toBeNull(); }); it('should not throw when closed called for manual triggers and closed popover', () => { const fixture = createTestComponent(`
`); const button = fixture.nativeElement.querySelector('button'); button.click(); // close fixture.detectChanges(); expect(getWindow(fixture.nativeElement)).toBeNull(); }); }); describe('Custom config', () => { let config: NgbPopoverConfig; beforeEach(() => { TestBed.configureTestingModule({imports: [NgbPopoverModule.forRoot()]}); TestBed.overrideComponent(TestComponent, {set: {template: `
`}}); }); beforeEach(inject([NgbPopoverConfig], (c: NgbPopoverConfig) => { config = c; config.placement = 'bottom'; config.triggers = 'hover'; config.container = 'body'; })); it('should initialize inputs with provided config', () => { const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const popover = fixture.componentInstance.popover; expect(popover.placement).toBe(config.placement); expect(popover.triggers).toBe(config.triggers); expect(popover.container).toBe(config.container); }); }); describe('Custom config as provider', () => { let config = new NgbPopoverConfig(); config.placement = 'bottom'; config.triggers = 'hover'; beforeEach(() => { TestBed.configureTestingModule( {imports: [NgbPopoverModule.forRoot()], providers: [{provide: NgbPopoverConfig, useValue: config}]}); }); it('should initialize inputs with provided config as provider', () => { const fixture = createTestComponent(`
`); const popover = fixture.componentInstance.popover; expect(popover.placement).toBe(config.placement); expect(popover.triggers).toBe(config.triggers); }); }); }); @Component({selector: 'test-cmpt', template: ``}) export class TestComponent { name = 'World'; show = true; title: string; placement: string; @ViewChild(NgbPopover) popover: NgbPopover; shown() {} hidden() {} } @Component({selector: 'test-onpush-cmpt', changeDetection: ChangeDetectionStrategy.OnPush, template: ``}) export class TestOnPushComponent { } @Component({selector: 'destroyable-cmpt', template: 'Some content'}) export class DestroyableCmpt implements OnDestroy { constructor(private _spyService: SpyService) {} ngOnDestroy(): void { this._spyService.called = true; } }