veza/apps/web/src/features/player/hooks/usePlayer.test.ts

301 lines
8.4 KiB
TypeScript
Raw Normal View History

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];
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];
expect(endedCallback).toBeDefined();
// Simulate track end
if (endedCallback) {
endedCallback();
}
// Should call next
expect(mockStore.next).toHaveBeenCalled();
});
});