/**
* Component tests for SvTooltip, exercising the shared createTooltip core: it
* opens after the show delay on hover, hides on Escape, respects `disabled`, and
* wires aria-describedby onto the focusable child while open.
*/
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'
import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
import SvTooltip from './SvTooltip.svelte'
const child = createRawSnippet(() => ({ render: () => `` }))
function mountTip(props: Record) {
const target = document.createElement('div')
document.body.appendChild(target)
const app = mount(SvTooltip, { target, props: { children: child, ...props } as any })
flushSync()
return { target, destroy: () => { unmount(app); target.remove() } }
}
const tip = () => document.body.querySelector('.sv-tip')
describe('SvTooltip', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
vi.useRealTimers()
document.querySelectorAll('.sv-tip').forEach((n) => n.remove())
})
it('shows after the delay on pointerenter and hides on Escape', () => {
const { target, destroy } = mountTip({ text: 'Delete row', delay: 300 })
try {
const anchor = target.querySelector('.sv-tip__anchor')!
expect(tip()).toBeNull()
anchor.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }))
// Nothing yet - still within the delay window.
flushSync()
expect(tip()).toBeNull()
vi.advanceTimersByTime(300)
flushSync()
const el = tip()!
expect(el).not.toBeNull()
expect(el.getAttribute('role')).toBe('tooltip')
expect(el.textContent).toContain('Delete row')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
flushSync()
expect(tip()).toBeNull()
} finally { destroy() }
})
it('does not show while disabled', () => {
const { target, destroy } = mountTip({ text: 'x', disabled: true })
try {
target.querySelector('.sv-tip__anchor')!
.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }))
vi.advanceTimersByTime(500)
flushSync()
expect(tip()).toBeNull()
} finally { destroy() }
})
it('wires aria-describedby onto the focusable child while open', () => {
const { target, destroy } = mountTip({ text: 'Delete row', delay: 100 })
try {
target.querySelector('.sv-tip__anchor')!
.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }))
vi.advanceTimersByTime(100)
flushSync()
const btn = target.querySelector('.btn')!
const describedby = btn.getAttribute('aria-describedby')
expect(describedby).toBeTruthy()
expect(document.getElementById(describedby!)?.getAttribute('role')).toBe('tooltip')
} finally { destroy() }
})
})