// FE-W4TIP-001 to FE-W4TIP-015 import { createRef, useRef } from 'react' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { render, screen, fireEvent, act } from '../../../tests/helpers/render' import Tooltip from './Tooltip' beforeEach(() => { vi.useFakeTimers({ shouldAdvanceTime: true }) }) afterEach(() => { vi.useRealTimers() vi.restoreAllMocks() }) function hover(el: HTMLElement) { fireEvent.mouseEnter(el) act(() => { vi.advanceTimersByTime(300) }) } describe('Tooltip', () => { it('FE-W4TIP-001: renders the trigger and no tooltip until hovered', () => { render() expect(screen.getByRole('button', { name: 'x' })).toBeInTheDocument() expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-002: opens only after the delay has elapsed', () => { render() fireEvent.mouseEnter(screen.getByRole('button')) act(() => { vi.advanceTimersByTime(200) }) expect(screen.queryByRole('tooltip')).toBeNull() act(() => { vi.advanceTimersByTime(100) }) expect(screen.getByRole('tooltip')).toHaveTextContent('Delete') }) it('FE-W4TIP-003: leaving cancels a pending open', () => { render() const trigger = screen.getByRole('button') fireEvent.mouseEnter(trigger) fireEvent.mouseLeave(trigger) act(() => { vi.advanceTimersByTime(500) }) expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-004: keyboard focus opens and blur closes the tooltip', () => { render() const trigger = screen.getByRole('button') vi.spyOn(trigger, 'matches').mockImplementation(sel => sel === ':focus-visible') fireEvent.focus(trigger) act(() => { vi.advanceTimersByTime(300) }) expect(screen.getByRole('tooltip')).toBeInTheDocument() fireEvent.blur(trigger) expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-014: focus handed back without the keyboard (a dialog closing) opens nothing', () => { render() const trigger = screen.getByRole('button') vi.spyOn(trigger, 'matches').mockReturnValue(false) fireEvent.focus(trigger) act(() => { vi.advanceTimersByTime(300) }) expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-005: a disabled tooltip never opens', () => { render() hover(screen.getByRole('button')) expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-006: an empty label never opens', () => { render() hover(screen.getByRole('button')) expect(screen.queryByRole('tooltip')).toBeNull() }) it('FE-W4TIP-007: still chains the original handlers of the child', () => { const onMouseEnter = vi.fn() const onMouseLeave = vi.fn() const onFocus = vi.fn() const onBlur = vi.fn() render( , ) const trigger = screen.getByRole('button') fireEvent.mouseEnter(trigger) fireEvent.mouseLeave(trigger) fireEvent.focus(trigger) fireEvent.blur(trigger) expect(onMouseEnter).toHaveBeenCalledOnce() expect(onMouseLeave).toHaveBeenCalledOnce() expect(onFocus).toHaveBeenCalledOnce() expect(onBlur).toHaveBeenCalledOnce() }) it('FE-W4TIP-008: forwards the node to an object ref on the child', () => { const ref = createRef() render() expect(ref.current).toBe(screen.getByRole('button')) }) it('FE-W4TIP-009: forwards the node to a callback ref on the child', () => { const seen: (HTMLElement | null)[] = [] render() expect(seen[0]).toBe(screen.getByRole('button')) }) it('FE-W4TIP-010: positions the tooltip per placement and clamps it into the viewport', () => { vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ top: 100, bottom: 130, left: 200, right: 260, width: 60, height: 30, x: 200, y: 100, toJSON: () => ({}), } as DOMRect) const { unmount } = render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ top: '136px' }) unmount() render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ left: '266px' }) }) it('FE-W4TIP-011: clamps a top/left placement to the viewport padding', () => { vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ top: 0, bottom: 20, left: 0, right: 20, width: 20, height: 20, x: 0, y: 0, toJSON: () => ({}), } as DOMRect) const { unmount } = render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ top: '6px' }) unmount() render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ left: '6px' }) }) it('FE-W4TIP-012: clears a pending timer when the trigger unmounts', () => { const clear = vi.spyOn(window, 'clearTimeout') const { unmount } = render() fireEvent.mouseEnter(screen.getByRole('button')) unmount() expect(clear).toHaveBeenCalled() }) }) describe('Tooltip — inside a component that owns the ref', () => { it('FE-W4TIP-013: keeps the owner ref pointing at the trigger', () => { function Owner() { const ref = useRef(null) return ( ) } render() fireEvent.click(screen.getByRole('button')) expect(screen.getByRole('button')).toHaveAttribute('data-clicked', '1') }) it('FE-W4TIP-015: a label of several lines breaks at them and stays within the window, a single line does not wrap', () => { const { unmount } = render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ whiteSpace: 'pre-line' }) expect(screen.getByRole('tooltip').style.maxWidth).toContain('100vw') unmount() render() hover(screen.getByRole('button')) expect(screen.getByRole('tooltip')).toHaveStyle({ whiteSpace: 'nowrap' }) }) })