import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { usePlayer } from './usePlayer'; import { usePlayerStore } from '../store/playerStore'; import { audioPlayerService } from '../services/playerService'; import type { Track } from '../types'; // Mock dependencies vi.mock('../store/playerStore', () => ({ usePlayerStore: vi.fn(), })); vi.mock('../services/playerService', () => ({ audioPlayerService: { initialize: vi.fn(), cleanup: vi.fn(), loadTrack: vi.fn(), play: vi.fn(), pause: vi.fn(), stop: vi.fn(), seek: vi.fn(), setVolume: vi.fn(), setMuted: vi.fn(), onTimeUpdate: vi.fn(), onDurationChange: vi.fn(), onEnded: vi.fn(), onError: vi.fn(), onPlay: vi.fn(), onPause: vi.fn(), }, })); describe('usePlayer', () => { const mockTrack: Track = { id: 1, title: 'Test Track', duration: 180, url: 'https://example.com/track.mp3', }; const mockStore = { currentTrack: null, isPlaying: false, currentTime: 0, duration: 0, volume: 100, muted: false, queue: [], currentIndex: -1, repeat: 'off' as const, shuffle: false, play: vi.fn(), pause: vi.fn(), resume: vi.fn(), stop: vi.fn(), next: vi.fn(), previous: vi.fn(), seek: vi.fn(), setCurrentTime: vi.fn(), setDuration: vi.fn(), setVolume: vi.fn(), toggleMute: vi.fn(), toggleShuffle: vi.fn(), setRepeat: vi.fn(), addToQueue: vi.fn(), clearQueue: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); vi.mocked(usePlayerStore).mockReturnValue(mockStore); vi.mocked(audioPlayerService.play).mockResolvedValue(undefined); vi.mocked(audioPlayerService.loadTrack).mockResolvedValue(undefined); }); afterEach(() => { vi.clearAllMocks(); }); it('should return player state', () => { const { result } = renderHook(() => usePlayer()); expect(result.current.currentTrack).toBeNull(); expect(result.current.isPlaying).toBe(false); expect(result.current.currentTime).toBe(0); expect(result.current.duration).toBe(0); expect(result.current.volume).toBe(100); expect(result.current.muted).toBe(false); expect(result.current.queue).toEqual([]); expect(result.current.currentIndex).toBe(-1); expect(result.current.repeat).toBe('off'); expect(result.current.shuffle).toBe(false); }); it('should return player controls', () => { const { result } = renderHook(() => usePlayer()); expect(typeof result.current.play).toBe('function'); expect(typeof result.current.pause).toBe('function'); expect(typeof result.current.resume).toBe('function'); expect(typeof result.current.stop).toBe('function'); expect(typeof result.current.next).toBe('function'); expect(typeof result.current.previous).toBe('function'); expect(typeof result.current.seek).toBe('function'); expect(typeof result.current.setVolume).toBe('function'); expect(typeof result.current.toggleMute).toBe('function'); expect(typeof result.current.toggleShuffle).toBe('function'); expect(typeof result.current.setRepeat).toBe('function'); expect(typeof result.current.addToQueue).toBe('function'); expect(typeof result.current.clearQueue).toBe('function'); }); it('should initialize audio service when audio element is provided', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; renderHook(() => usePlayer(audioRef)); expect(audioPlayerService.initialize).toHaveBeenCalledWith(audioElement); }); it('should cleanup audio service on unmount', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; const { unmount } = renderHook(() => usePlayer(audioRef)); unmount(); expect(audioPlayerService.cleanup).toHaveBeenCalled(); }); it('should load track when currentTrack changes', async () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; vi.mocked(usePlayerStore).mockReturnValue({ ...mockStore, currentTrack: mockTrack, }); renderHook(() => usePlayer(audioRef)); await waitFor(() => { expect(audioPlayerService.loadTrack).toHaveBeenCalledWith(mockTrack); }); }); it('should sync playback state with audio element', async () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; vi.mocked(usePlayerStore).mockReturnValue({ ...mockStore, isPlaying: true, }); renderHook(() => usePlayer(audioRef)); await waitFor(() => { expect(audioPlayerService.play).toHaveBeenCalled(); }); }); it('should sync volume and mute with audio element', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; vi.mocked(usePlayerStore).mockReturnValue({ ...mockStore, volume: 50, muted: false, }); renderHook(() => usePlayer(audioRef)); expect(audioPlayerService.setVolume).toHaveBeenCalledWith(0.5); expect(audioPlayerService.setMuted).toHaveBeenCalledWith(false); }); it('should handle play with track', async () => { const { result } = renderHook(() => usePlayer()); await result.current.play(mockTrack); expect(mockStore.play).toHaveBeenCalledWith(mockTrack); }); it('should handle pause', () => { const { result } = renderHook(() => usePlayer()); result.current.pause(); expect(mockStore.pause).toHaveBeenCalled(); }); it('should handle resume', async () => { const { result } = renderHook(() => usePlayer()); await result.current.resume(); expect(mockStore.resume).toHaveBeenCalled(); }); it('should handle stop', () => { const { result } = renderHook(() => usePlayer()); result.current.stop(); expect(mockStore.stop).toHaveBeenCalled(); }); it('should handle seek', () => { const { result } = renderHook(() => usePlayer()); result.current.seek(50); expect(mockStore.seek).toHaveBeenCalledWith(50); }); it('should handle setVolume', () => { const { result } = renderHook(() => usePlayer()); result.current.setVolume(75); expect(mockStore.setVolume).toHaveBeenCalledWith(75); }); it('should handle toggleMute', () => { const { result } = renderHook(() => usePlayer()); result.current.toggleMute(); expect(mockStore.toggleMute).toHaveBeenCalled(); }); it('should setup event callbacks', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; renderHook(() => usePlayer(audioRef)); expect(audioPlayerService.onTimeUpdate).toHaveBeenCalled(); expect(audioPlayerService.onDurationChange).toHaveBeenCalled(); expect(audioPlayerService.onEnded).toHaveBeenCalled(); expect(audioPlayerService.onError).toHaveBeenCalled(); expect(audioPlayerService.onPlay).toHaveBeenCalled(); expect(audioPlayerService.onPause).toHaveBeenCalled(); }); it('should handle track end with repeat track mode', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; vi.mocked(usePlayerStore).mockReturnValue({ ...mockStore, repeat: 'track' as const, }); renderHook(() => usePlayer(audioRef)); // Get the ended callback const endedCallback = vi.mocked(audioPlayerService.onEnded).mock .calls[0][0]; expect(endedCallback).toBeDefined(); // Simulate track end if (endedCallback) { endedCallback(); } // Should seek to start and play again (handled by the callback) expect(audioPlayerService.seek).toHaveBeenCalledWith(0); }); it('should handle track end with repeat off', () => { const audioElement = document.createElement('audio'); const audioRef = { current: audioElement }; vi.mocked(usePlayerStore).mockReturnValue({ ...mockStore, repeat: 'off' as const, }); renderHook(() => usePlayer(audioRef)); // Get the ended callback const endedCallback = vi.mocked(audioPlayerService.onEnded).mock .calls[0][0]; expect(endedCallback).toBeDefined(); // Simulate track end if (endedCallback) { endedCallback(); } // Should call next expect(mockStore.next).toHaveBeenCalled(); }); });