import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import { autoplay } from '../src/autoplay.ts'; function fakeCarousel(count = 3) { let index = 0; return { get index() { return index; }, count, next: vi.fn(() => { index = (index + 1) % count; }), prev: vi.fn(), scrollToIndex: vi.fn(), on: () => () => {}, destroy: vi.fn(), }; } // jsdom has no matchMedia — stub it. reduce = whether reduce-motion matches. function stubMatchMedia(reduce: boolean) { vi.stubGlobal('matchMedia', (q: string) => ({ matches: reduce && q.includes('reduce'), media: q, addEventListener() {}, removeEventListener() {}, })); } beforeEach(() => { vi.useFakeTimers(); stubMatchMedia(false); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); test('advances every interval', () => { const c = fakeCarousel(); autoplay(c as never, { interval: 1000, root: document.createElement('div') }); vi.advanceTimersByTime(2000); expect(c.next).toHaveBeenCalledTimes(2); }); test('pause on pointerenter, resume on pointerleave', () => { const c = fakeCarousel(); const root = document.createElement('div'); autoplay(c as never, { interval: 1000, root }); root.dispatchEvent(new Event('pointerenter')); vi.advanceTimersByTime(3000); expect(c.next).not.toHaveBeenCalled(); root.dispatchEvent(new Event('pointerleave')); vi.advanceTimersByTime(1000); expect(c.next).toHaveBeenCalledTimes(1); }); test('does not start under prefers-reduced-motion', () => { stubMatchMedia(true); const c = fakeCarousel(); autoplay(c as never, { interval: 1000, root: document.createElement('div') }); vi.advanceTimersByTime(5000); expect(c.next).not.toHaveBeenCalled(); }); test('stop() halts and removes listeners', () => { const c = fakeCarousel(); const stop = autoplay(c as never, { interval: 1000, root: document.createElement('div') }); stop(); vi.advanceTimersByTime(5000); expect(c.next).not.toHaveBeenCalled(); });