import React from 'react'; import { describe, it, expect, vi } from 'vitest'; import { render, fireEvent } from '@testing-library/react'; import { PixelBounce } from '../../animations/PixelBounce'; import { mockMatchMedia } from './matchmedia-mock'; /** * jsdom lacks native CSS animation support, so React registers the * vendor-prefixed `webkitAnimationEnd` event there. Dispatch both variants — * exactly one is registered, so the handler fires exactly once. */ function dispatchAnimationEnd(el: Element) { fireEvent.animationEnd(el); el.dispatchEvent(new Event('webkitAnimationEnd', { bubbles: true })); } describe('PixelBounce', () => { it('renders children inside the wrapper', () => { const { getByText } = render(bounce me); expect(getByText('bounce me')).toBeTruthy(); }); it('applies the pxl-bounce animation on mount with default duration', () => { const { container } = render(x); const el = container.firstElementChild as HTMLElement; expect(el.style.animation).toContain('pxl-bounce'); expect(el.style.animation).toContain('800ms'); expect(el.style.animation).toContain('infinite'); }); it('reflects duration, repeat and height props in inline styles', () => { const { container } = render( x, ); const el = container.firstElementChild as HTMLElement; expect(el.style.animation).toContain('500ms'); expect(el.style.animation).toContain(' 3 '); expect(el.style.getPropertyValue('--pxl-bounce-height')).toBe('16px'); }); it('does not animate when trigger={false} (controlled off)', () => { const { container } = render(x); const el = container.firstElementChild as HTMLElement; expect(el.style.animation).toBe(''); }); it('trigger="click" only animates after a click', () => { const { container } = render(x); const el = container.firstElementChild as HTMLElement; expect(el.style.animation).toBe(''); fireEvent.click(el); expect(el.style.animation).toContain('pxl-bounce'); }); it('fires onComplete when the animation ends on the wrapper', () => { const onComplete = vi.fn(); const { container } = render( x, ); const el = container.firstElementChild as HTMLElement; dispatchAnimationEnd(el); expect(onComplete).toHaveBeenCalledTimes(1); }); it('renders children statically (no animation) when the user prefers reduced motion', () => { const ctl = mockMatchMedia(true); try { const { container, getByText } = render(calm); const el = container.firstElementChild as HTMLElement; expect(el.style.animation).toBe(''); expect(getByText('calm')).toBeTruthy(); } finally { ctl.restore(); } }); it('merges className and injects the shared keyframes stylesheet once', () => { const { container } = render(x); const el = container.firstElementChild as HTMLElement; expect(el.className).toContain('inline-block'); expect(el.className).toContain('custom'); expect(document.querySelectorAll('#pxl-anims')).toHaveLength(1); }); });