// @vitest-environment happy-dom
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { Tooltip } from './tooltip';
const fixture = `
Hover me!
Hover me!
`;
describe('Tooltip (Popover API)', () => {
beforeEach(() => {
// Setup fake timers for delay assertions
vi.useFakeTimers();
// Polyfill popover methods if Happy-DOM environment does not support native Popover API yet
if (!HTMLElement.prototype.showPopover) {
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
this.setAttribute('popover-open', '');
this.matches = (selector: string) =>
selector === ':popover-open' || selector === '[popover]';
});
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
this.removeAttribute('popover-open');
this.matches = (selector: string) => selector === '[popover]';
});
}
// Set up document fixture
document.body.innerHTML = fixture;
// Initialize tooltips
Tooltip.init(document.querySelectorAll('.tooltipped'), {
enterDelay: 0,
exitDelay: 0
});
});
afterEach(() => {
document.body.innerHTML = '';
vi.restoreAllMocks();
vi.useRealTimers();
});
describe('opens and closes properly', () => {
it('shows tooltip on mouse enter and hides on mouse leave', () => {
const tooltippedBtn = document.querySelector('#test')!;
const instance = Tooltip.getInstance(tooltippedBtn);
const tooltip = instance.tooltipEl;
// Simulate mouse enter
tooltippedBtn.dispatchEvent(new Event('mouseenter'));
vi.advanceTimersByTime(10);
// Verify content and open state
expect(instance.isOpen).toBe(true);
expect(tooltip.textContent).toBe('I am tooltip');
// Simulate mouse leave
tooltippedBtn.dispatchEvent(new Event('mouseleave'));
vi.advanceTimersByTime(10);
expect(instance.isOpen).toBe(false);
});
it('removes tooltip DOM object on destroy', () => {
const tooltippedBtn = document.querySelector('#test1')!;
const instance = Tooltip.getInstance(tooltippedBtn);
const tooltipEl = instance.tooltipEl;
instance.destroy();
// Check DOM cleanup & instance ref clearance
expect(Tooltip.getInstance(tooltippedBtn)).toBeUndefined();
expect(document.body.contains(tooltipEl)).toBe(false);
});
it('changes position attribute dynamically', () => {
const tooltippedBtn = document.querySelector('#test')!;
tooltippedBtn.setAttribute('data-position', 'right');
const instance = Tooltip.getInstance(tooltippedBtn);
tooltippedBtn.dispatchEvent(new Event('mouseenter'));
vi.advanceTimersByTime(10);
expect(instance.tooltipEl.dataset.position).toBe('right');
});
it('accepts delay option from javascript initialization', () => {
const tooltippedBtn = document.querySelector('#test')!;
tooltippedBtn.removeAttribute('data-delay');
// Re-initialize with a custom enterDelay
const instance = Tooltip.init(tooltippedBtn, { enterDelay: 200 });
tooltippedBtn.dispatchEvent(new Event('mouseenter'));
// Check before delay completes
vi.advanceTimersByTime(150);
expect(instance.isOpen).toBe(false);
// Check after delay passes
vi.advanceTimersByTime(100);
expect(instance.isOpen).toBe(true);
});
it('assigns correctly with a fixed position parent', () => {
const tooltippedBtn = document.querySelector('#test2')!;
const instance = Tooltip.getInstance(tooltippedBtn);
tooltippedBtn.dispatchEvent(new Event('mouseenter'));
vi.advanceTimersByTime(10);
expect(instance.isOpen).toBe(true);
expect(instance.tooltipEl.dataset.position).toBe('bottom');
});
});
});