2025-12-03 21:56:50 +00:00
|
|
|
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
|
2025-12-13 02:34:34 +00:00
|
|
|
const endedCallback = vi.mocked(audioPlayerService.onEnded).mock
|
|
|
|
|
.calls[0][0];
|
2025-12-03 21:56:50 +00:00
|
|
|
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
|
2025-12-13 02:34:34 +00:00
|
|
|
const endedCallback = vi.mocked(audioPlayerService.onEnded).mock
|
|
|
|
|
.calls[0][0];
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(endedCallback).toBeDefined();
|
|
|
|
|
|
|
|
|
|
// Simulate track end
|
|
|
|
|
if (endedCallback) {
|
|
|
|
|
endedCallback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should call next
|
|
|
|
|
expect(mockStore.next).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|