1 import {TestBed, ComponentFixture, inject} from '@angular/core/testing';
2 import {createGenericTestComponent} from '../test/common';
4 import {By} from '@angular/platform-browser';
5 import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core';
7 import {PlxTooltipModule} from './plx-tooltip.module';
8 import {PlxTooltipWindow, PlxTooltip} from './plx-tooltip';
9 import {PlxTooltipConfig} from './plx-tooltip-config';
11 const createTestComponent =
12 (html: string) => <ComponentFixture<TestComponent>>createGenericTestComponent(html, TestComponent);
14 const createOnPushTestComponent =
15 (html: string) => <ComponentFixture<TestOnPushComponent>>createGenericTestComponent(html, TestOnPushComponent);
17 describe('plx-tooltip-window', () => {
18 beforeEach(() => { TestBed.configureTestingModule({imports: [PlxTooltipModule.forRoot()]}); });
20 it('should render tooltip on top by default', () => {
21 const fixture = TestBed.createComponent(PlxTooltipWindow);
22 fixture.detectChanges();
24 expect(fixture.nativeElement).toHaveCssClass('tooltip');
25 expect(fixture.nativeElement).toHaveCssClass('tooltip-top');
26 expect(fixture.nativeElement.getAttribute('role')).toBe('tooltip');
29 it('should position tooltips as requested', () => {
30 const fixture = TestBed.createComponent(PlxTooltipWindow);
31 fixture.componentInstance.placement = 'left';
32 fixture.detectChanges();
33 expect(fixture.nativeElement).toHaveCssClass('tooltip-left');
37 describe('plx-tooltip', () => {
40 TestBed.configureTestingModule(
41 {declarations: [TestComponent, TestOnPushComponent], imports: [PlxTooltipModule.forRoot()]});
44 function getWindow(element) { return element.querySelector('plx-tooltip-window'); }
46 describe('basic functionality', () => {
48 it('should open and close a tooltip - default settings and content as string', () => {
49 const fixture = createTestComponent(`<div plxTooltip="Great tip!"></div>`);
50 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
51 const defaultConfig = new PlxTooltipConfig();
53 directive.triggerEventHandler('mouseenter', {});
54 fixture.detectChanges();
55 const windowEl = getWindow(fixture.nativeElement);
57 expect(windowEl).toHaveCssClass('tooltip');
58 expect(windowEl).toHaveCssClass(`tooltip-${defaultConfig.placement}`);
59 expect(windowEl.textContent.trim()).toBe('Great tip!');
60 expect(windowEl.getAttribute('role')).toBe('tooltip');
61 expect(windowEl.getAttribute('id')).toBe('plx-tooltip-0');
62 expect(windowEl.parentNode).toBe(fixture.nativeElement);
63 expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('plx-tooltip-0');
65 directive.triggerEventHandler('mouseleave', {});
66 fixture.detectChanges();
67 expect(getWindow(fixture.nativeElement)).toBeNull();
68 expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull();
71 it('should open and close a tooltip - default settings and content from a template', () => {
72 const fixture = createTestComponent(`<template #t>Hello, {{name}}!</template><div [plxTooltip]="t"></div>`);
73 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
75 directive.triggerEventHandler('mouseenter', {});
76 fixture.detectChanges();
77 const windowEl = getWindow(fixture.nativeElement);
79 expect(windowEl).toHaveCssClass('tooltip');
80 expect(windowEl).toHaveCssClass('tooltip-top');
81 expect(windowEl.textContent.trim()).toBe('Hello, World!');
82 expect(windowEl.getAttribute('role')).toBe('tooltip');
83 expect(windowEl.getAttribute('id')).toBe('plx-tooltip-1');
84 expect(windowEl.parentNode).toBe(fixture.nativeElement);
85 expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('plx-tooltip-1');
87 directive.triggerEventHandler('mouseleave', {});
88 fixture.detectChanges();
89 expect(getWindow(fixture.nativeElement)).toBeNull();
90 expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull();
93 it('should open and close a tooltip - default settings, content from a template and context supplied', () => {
94 const fixture = createTestComponent(`<template #t let-name="name">Hello, {{name}}!</template><div [plxTooltip]="t"></div>`);
95 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
97 directive.context.tooltip.open({name: 'John'});
98 fixture.detectChanges();
99 const windowEl = getWindow(fixture.nativeElement);
101 expect(windowEl).toHaveCssClass('tooltip');
102 expect(windowEl).toHaveCssClass('tooltip-top');
103 expect(windowEl.textContent.trim()).toBe('Hello, John!');
104 expect(windowEl.getAttribute('role')).toBe('tooltip');
105 expect(windowEl.getAttribute('id')).toBe('plx-tooltip-2');
106 expect(windowEl.parentNode).toBe(fixture.nativeElement);
107 expect(directive.nativeElement.getAttribute('aria-describedby')).toBe('plx-tooltip-2');
109 directive.triggerEventHandler('mouseleave', {});
110 fixture.detectChanges();
111 expect(getWindow(fixture.nativeElement)).toBeNull();
112 expect(directive.nativeElement.getAttribute('aria-describedby')).toBeNull();
115 it('should not open a tooltip if content is falsy', () => {
116 const fixture = createTestComponent(`<div [plxTooltip]="notExisting"></div>`);
117 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
119 directive.triggerEventHandler('mouseenter', {});
120 fixture.detectChanges();
121 const windowEl = getWindow(fixture.nativeElement);
123 expect(windowEl).toBeNull();
126 it('should close the tooltip tooltip if content becomes falsy', () => {
127 const fixture = createTestComponent(`<div [plxTooltip]="name"></div>`);
128 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
130 directive.triggerEventHandler('mouseenter', {});
131 fixture.detectChanges();
132 expect(getWindow(fixture.nativeElement)).not.toBeNull();
134 fixture.componentInstance.name = null;
135 fixture.detectChanges();
136 expect(getWindow(fixture.nativeElement)).toBeNull();
139 it('should allow re-opening previously closed tooltips', () => {
140 const fixture = createTestComponent(`<div plxTooltip="Great tip!"></div>`);
141 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
143 directive.triggerEventHandler('mouseenter', {});
144 fixture.detectChanges();
145 expect(getWindow(fixture.nativeElement)).not.toBeNull();
147 directive.triggerEventHandler('mouseleave', {});
148 fixture.detectChanges();
149 expect(getWindow(fixture.nativeElement)).toBeNull();
151 directive.triggerEventHandler('mouseenter', {});
152 fixture.detectChanges();
153 expect(getWindow(fixture.nativeElement)).not.toBeNull();
156 it('should not leave dangling tooltips in the DOM', () => {
157 const fixture = createTestComponent(`<template [ngIf]="show"><div plxTooltip="Great tip!"></div></template>`);
158 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
160 directive.triggerEventHandler('mouseenter', {});
161 fixture.detectChanges();
162 expect(getWindow(fixture.nativeElement)).not.toBeNull();
164 fixture.componentInstance.show = false;
165 fixture.detectChanges();
166 expect(getWindow(fixture.nativeElement)).toBeNull();
169 it('should properly cleanup tooltips with manual triggers', () => {
170 const fixture = createTestComponent(`
171 <template [ngIf]="show">
172 <div plxTooltip="Great tip!" triggers="manual" #t="plxTooltip" (mouseenter)="t.open()"></div>
174 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
176 directive.triggerEventHandler('mouseenter', {});
177 fixture.detectChanges();
178 expect(getWindow(fixture.nativeElement)).not.toBeNull();
180 fixture.componentInstance.show = false;
181 fixture.detectChanges();
182 expect(getWindow(fixture.nativeElement)).toBeNull();
185 describe('positioning', () => {
187 it('should use requested position', () => {
188 const fixture = createTestComponent(`<div plxTooltip="Great tip!" placement="left"></div>`);
189 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
191 directive.triggerEventHandler('mouseenter', {});
192 fixture.detectChanges();
193 const windowEl = getWindow(fixture.nativeElement);
195 expect(windowEl).toHaveCssClass('tooltip');
196 expect(windowEl).toHaveCssClass('tooltip-left');
197 expect(windowEl.textContent.trim()).toBe('Great tip!');
200 it('should properly position tooltips when a component is using the OnPush strategy', () => {
201 const fixture = createOnPushTestComponent(`<div plxTooltip="Great tip!" placement="left"></div>`);
202 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
204 directive.triggerEventHandler('mouseenter', {});
205 fixture.detectChanges();
206 const windowEl = getWindow(fixture.nativeElement);
208 expect(windowEl).toHaveCssClass('tooltip');
209 expect(windowEl).toHaveCssClass('tooltip-left');
210 expect(windowEl.textContent.trim()).toBe('Great tip!');
214 describe('triggers', () => {
216 it('should support toggle triggers', () => {
217 const fixture = createTestComponent(`<div plxTooltip="Great tip!" triggers="click"></div>`);
218 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
220 directive.triggerEventHandler('click', {});
221 fixture.detectChanges();
222 expect(getWindow(fixture.nativeElement)).not.toBeNull();
224 directive.triggerEventHandler('click', {});
225 fixture.detectChanges();
226 expect(getWindow(fixture.nativeElement)).toBeNull();
229 it('should non-default toggle triggers', () => {
230 const fixture = createTestComponent(`<div plxTooltip="Great tip!" triggers="mouseenter:click"></div>`);
231 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
233 directive.triggerEventHandler('mouseenter', {});
234 fixture.detectChanges();
235 expect(getWindow(fixture.nativeElement)).not.toBeNull();
237 directive.triggerEventHandler('click', {});
238 fixture.detectChanges();
239 expect(getWindow(fixture.nativeElement)).toBeNull();
242 it('should support multiple triggers', () => {
244 createTestComponent(`<div plxTooltip="Great tip!" triggers="mouseenter:mouseleave click"></div>`);
245 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
247 directive.triggerEventHandler('mouseenter', {});
248 fixture.detectChanges();
249 expect(getWindow(fixture.nativeElement)).not.toBeNull();
251 directive.triggerEventHandler('click', {});
252 fixture.detectChanges();
253 expect(getWindow(fixture.nativeElement)).toBeNull();
256 it('should not use default for manual triggers', () => {
257 const fixture = createTestComponent(`<div plxTooltip="Great tip!" triggers="manual"></div>`);
258 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
260 directive.triggerEventHandler('mouseenter', {});
261 fixture.detectChanges();
262 expect(getWindow(fixture.nativeElement)).toBeNull();
265 it('should allow toggling for manual triggers', () => {
266 const fixture = createTestComponent(`
267 <div plxTooltip="Great tip!" triggers="manual" #t="plxTooltip"></div>
268 <button (click)="t.toggle()">T</button>`);
269 const button = fixture.nativeElement.querySelector('button');
272 fixture.detectChanges();
273 expect(getWindow(fixture.nativeElement)).not.toBeNull();
276 fixture.detectChanges();
277 expect(getWindow(fixture.nativeElement)).toBeNull();
280 it('should allow open / close for manual triggers', () => {
281 const fixture = createTestComponent(`
282 <div plxTooltip="Great tip!" triggers="manual" #t="plxTooltip"></div>
283 <button (click)="t.open()">O</button>
284 <button (click)="t.close()">C</button>`);
286 const buttons = fixture.nativeElement.querySelectorAll('button');
288 buttons[0].click(); // open
289 fixture.detectChanges();
290 expect(getWindow(fixture.nativeElement)).not.toBeNull();
292 buttons[1].click(); // close
293 fixture.detectChanges();
294 expect(getWindow(fixture.nativeElement)).toBeNull();
297 it('should not throw when open called for manual triggers and open tooltip', () => {
298 const fixture = createTestComponent(`
299 <div plxTooltip="Great tip!" triggers="manual" #t="plxTooltip"></div>
300 <button (click)="t.open()">O</button>`);
301 const button = fixture.nativeElement.querySelector('button');
303 button.click(); // open
304 fixture.detectChanges();
305 expect(getWindow(fixture.nativeElement)).not.toBeNull();
307 button.click(); // open
308 fixture.detectChanges();
309 expect(getWindow(fixture.nativeElement)).not.toBeNull();
312 it('should not throw when closed called for manual triggers and closed tooltip', () => {
313 const fixture = createTestComponent(`
314 <div plxTooltip="Great tip!" triggers="manual" #t="plxTooltip"></div>
315 <button (click)="t.close()">C</button>`);
317 const button = fixture.nativeElement.querySelector('button');
319 button.click(); // close
320 fixture.detectChanges();
321 expect(getWindow(fixture.nativeElement)).toBeNull();
326 describe('container', () => {
328 it('should be appended to the element matching the selector passed to "container"', () => {
329 const selector = 'body';
330 const fixture = createTestComponent(`<div plxTooltip="Great tip!" container="` + selector + `"></div>`);
331 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
333 directive.triggerEventHandler('mouseenter', {});
334 fixture.detectChanges();
335 expect(getWindow(fixture.nativeElement)).toBeNull();
336 expect(getWindow(document.querySelector(selector))).not.toBeNull();
339 it('should properly destroy tooltips when the "container" option is used', () => {
340 const selector = 'body';
342 createTestComponent(`<div *ngIf="show" plxTooltip="Great tip!" container="` + selector + `"></div>`);
343 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
345 directive.triggerEventHandler('mouseenter', {});
346 fixture.detectChanges();
348 expect(getWindow(document.querySelector(selector))).not.toBeNull();
349 fixture.componentRef.instance.show = false;
350 fixture.detectChanges();
351 expect(getWindow(document.querySelector(selector))).toBeNull();
355 describe('visibility', () => {
356 it('should emit events when showing and hiding popover', () => {
357 const fixture = createTestComponent(
358 `<div plxTooltip="Great tip!" triggers="click" (shown)="shown()" (hidden)="hidden()"></div>`);
359 const directive = fixture.debugElement.query(By.directive(PlxTooltip));
361 let shownSpy = spyOn(fixture.componentInstance, 'shown');
362 let hiddenSpy = spyOn(fixture.componentInstance, 'hidden');
364 directive.triggerEventHandler('click', {});
365 fixture.detectChanges();
366 expect(getWindow(fixture.nativeElement)).not.toBeNull();
367 expect(shownSpy).toHaveBeenCalled();
369 directive.triggerEventHandler('click', {});
370 fixture.detectChanges();
371 expect(getWindow(fixture.nativeElement)).toBeNull();
372 expect(hiddenSpy).toHaveBeenCalled();
375 it('should not emit close event when already closed', () => {
376 const fixture = createTestComponent(
377 `<div plxTooltip="Great tip!" triggers="manual" (shown)="shown()" (hidden)="hidden()"></div>`);
379 let shownSpy = spyOn(fixture.componentInstance, 'shown');
380 let hiddenSpy = spyOn(fixture.componentInstance, 'hidden');
382 fixture.componentInstance.tooltip.open();
383 fixture.detectChanges();
385 fixture.componentInstance.tooltip.open();
386 fixture.detectChanges();
388 expect(getWindow(fixture.nativeElement)).not.toBeNull();
389 expect(shownSpy).toHaveBeenCalled();
390 expect(shownSpy.calls.count()).toEqual(1);
391 expect(hiddenSpy).not.toHaveBeenCalled();
394 it('should not emit open event when already opened', () => {
395 const fixture = createTestComponent(
396 `<div plxTooltip="Great tip!" triggers="manual" (shown)="shown()" (hidden)="hidden()"></div>`);
398 let shownSpy = spyOn(fixture.componentInstance, 'shown');
399 let hiddenSpy = spyOn(fixture.componentInstance, 'hidden');
401 fixture.componentInstance.tooltip.close();
402 fixture.detectChanges();
403 expect(getWindow(fixture.nativeElement)).toBeNull();
404 expect(shownSpy).not.toHaveBeenCalled();
405 expect(hiddenSpy).toHaveBeenCalled();
408 it('should report correct visibility', () => {
409 const fixture = createTestComponent(`<div plxTooltip="Great tip!" triggers="manual"></div>`);
410 fixture.detectChanges();
412 expect(fixture.componentInstance.tooltip.isOpen()).toBeFalsy();
414 fixture.componentInstance.tooltip.open();
415 fixture.detectChanges();
416 expect(fixture.componentInstance.tooltip.isOpen()).toBeTruthy();
418 fixture.componentInstance.tooltip.close();
419 fixture.detectChanges();
420 expect(fixture.componentInstance.tooltip.isOpen()).toBeFalsy();
424 describe('Custom config', () => {
425 let config: PlxTooltipConfig;
428 TestBed.configureTestingModule({imports: [PlxTooltipModule.forRoot()]});
429 TestBed.overrideComponent(TestComponent, {set: {template: `<div plxTooltip="Great tip!"></div>`}});
432 beforeEach(inject([PlxTooltipConfig], (c: PlxTooltipConfig) => {
434 config.placement = 'bottom';
435 config.triggers = 'click';
436 config.container = 'body';
439 it('should initialize inputs with provided config', () => {
440 const fixture = TestBed.createComponent(TestComponent);
441 fixture.detectChanges();
442 const tooltip = fixture.componentInstance.tooltip;
444 expect(tooltip.placement).toBe(config.placement);
445 expect(tooltip.triggers).toBe(config.triggers);
446 expect(tooltip.container).toBe(config.container);
450 describe('Custom config as provider', () => {
451 let config = new PlxTooltipConfig();
452 config.placement = 'bottom';
453 config.triggers = 'click';
454 config.container = 'body';
457 TestBed.configureTestingModule(
458 {imports: [PlxTooltipModule.forRoot()], providers: [{provide: PlxTooltipConfig, useValue: config}]});
461 it('should initialize inputs with provided config as provider', () => {
462 const fixture = createTestComponent(`<div plxTooltip="Great tip!"></div>`);
463 const tooltip = fixture.componentInstance.tooltip;
465 expect(tooltip.placement).toBe(config.placement);
466 expect(tooltip.triggers).toBe(config.triggers);
467 expect(tooltip.container).toBe(config.container);
472 @Component({selector: 'test-cmpt', template: ``})
473 export class TestComponent {
477 @ViewChild(PlxTooltip) tooltip: PlxTooltip;
483 @Component({selector: 'test-onpush-cmpt', changeDetection: ChangeDetectionStrategy.OnPush, template: ``})
484 export class TestOnPushComponent {