import { renderHook } from '@testing-library/react'; import { act } from 'react'; import useVideoControls from '../useVideoControls'; describe('useVideoControls', () => { let mockVideoElement: any; let mockVideoRef: any; beforeEach(() => { mockVideoElement = { load: jest.fn(), play: jest.fn().mockResolvedValue(undefined), pause: jest.fn(), muted: true, addEventListener: jest.fn(), removeEventListener: jest.fn(), }; mockVideoRef = { current: mockVideoElement }; jest.clearAllMocks(); }); // ─── Initial state ──────────────────────────────────────────────────────── it('initializes with isPaused as true when autoplay is not set', () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); expect(result.current.isPaused).toBe(true); }); it('initializes with isMuted as true', () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); expect(result.current.isMuted).toBe(true); }); it('calls video.load() on mount', () => { renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); expect(mockVideoElement.load).toHaveBeenCalledTimes(1); }); // ─── URL change ─────────────────────────────────────────────────────────── it('calls video.load() again when the URL changes', () => { const { rerender } = renderHook(({ url }) => useVideoControls(mockVideoRef, url), { initialProps: { url: 'video1.mp4' }, }); act(() => { rerender({ url: 'video2.mp4' }); }); // load is called once on mount and once when the URL changes expect(mockVideoElement.load).toHaveBeenCalledTimes(2); }); it('sets isPaused back to true when the URL changes without autoplay', () => { const { result, rerender } = renderHook(({ url }) => useVideoControls(mockVideoRef, url), { initialProps: { url: 'video1.mp4' }, }); // Play first act(() => void result.current.playVideo()); act(() => { rerender({ url: 'video2.mp4' }); }); expect(result.current.isPaused).toBe(true); }); // ─── Autoplay ───────────────────────────────────────────────────────────── it('registers a loadeddata listener when autoplay is true', () => { renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true)); expect(mockVideoElement.addEventListener).toHaveBeenCalledWith( 'loadeddata', expect.any(Function), { once: true }, ); }); it('plays and sets isPaused to false when loadeddata fires with autoplay', async () => { renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true)); const [, loadedCallback] = mockVideoElement.addEventListener.mock.calls.find( ([event]: [string]) => event === 'loadeddata', ); await act(async () => { await loadedCallback(); }); expect(mockVideoElement.play).toHaveBeenCalled(); }); it('removes the loadeddata listener on unmount with autoplay', () => { const { unmount } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true)); unmount(); expect(mockVideoElement.removeEventListener).toHaveBeenCalledWith( 'loadeddata', expect.any(Function), ); }); it('does not register a loadeddata listener when autoplay is false', () => { renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', false)); const loadedCalls = mockVideoElement.addEventListener.mock.calls.filter( ([event]: [string]) => event === 'loadeddata', ); expect(loadedCalls).toHaveLength(0); }); // ─── playVideo ──────────────────────────────────────────────────────────── it('calls video.play() and sets isPaused to false', async () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); await act(async () => { await result.current.playVideo(); }); expect(mockVideoElement.play).toHaveBeenCalled(); expect(result.current.isPaused).toBe(false); }); it('sets isPaused to true and throws when play() rejects', async () => { mockVideoElement.play.mockRejectedValueOnce(new Error('NotAllowedError')); const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); await expect( act(async () => { await result.current.playVideo(); }), ).rejects.toThrow('Error playing video. Please try again later.'); expect(result.current.isPaused).toBe(true); }); // ─── pauseVideo ─────────────────────────────────────────────────────────── it('calls video.pause() and sets isPaused to true', () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); act(() => { result.current.pauseVideo(); }); expect(mockVideoElement.pause).toHaveBeenCalled(); expect(result.current.isPaused).toBe(true); }); // ─── toggleMute ─────────────────────────────────────────────────────────── it('toggleMute flips isMuted from true to false and updates video.muted', () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); expect(result.current.isMuted).toBe(true); act(() => { result.current.toggleMute(); }); expect(result.current.isMuted).toBe(false); expect(mockVideoElement.muted).toBe(false); }); it('toggleMute flips isMuted back to true on second call', () => { const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4')); act(() => { result.current.toggleMute(); }); act(() => { result.current.toggleMute(); }); expect(result.current.isMuted).toBe(true); expect(mockVideoElement.muted).toBe(true); }); it('toggleMute does nothing when videoRef.current is null', () => { const nullRef = { current: null }; const { result } = renderHook(() => useVideoControls(nullRef as any, 'test.mp4')); // Should not throw act(() => { result.current.toggleMute(); }); expect(result.current.isMuted).toBe(true); }); });