461 lines
13 KiB
TypeScript
461 lines
13 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { act, renderHook } from '@testing-library/react';
|
|
import { usePlayerStore } from './playerStore';
|
|
import type { Track } from '../types';
|
|
|
|
describe('playerStore', () => {
|
|
const mockTrack1: Track = {
|
|
id: 1,
|
|
title: 'Track 1',
|
|
duration: 180,
|
|
url: 'https://example.com/track1.mp3',
|
|
};
|
|
|
|
const mockTrack2: Track = {
|
|
id: 2,
|
|
title: 'Track 2',
|
|
duration: 200,
|
|
url: 'https://example.com/track2.mp3',
|
|
};
|
|
|
|
const mockTrack3: Track = {
|
|
id: 3,
|
|
title: 'Track 3',
|
|
duration: 220,
|
|
url: 'https://example.com/track3.mp3',
|
|
};
|
|
|
|
beforeEach(() => {
|
|
// Reset store before each test
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
act(() => {
|
|
result.current.clearQueue();
|
|
result.current.stop();
|
|
});
|
|
});
|
|
|
|
describe('Initial state', () => {
|
|
it('should have correct initial state', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
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);
|
|
});
|
|
});
|
|
|
|
describe('Play actions', () => {
|
|
it('should play a track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack1);
|
|
expect(result.current.isPlaying).toBe(true);
|
|
expect(result.current.currentTime).toBe(0);
|
|
expect(result.current.queue).toContainEqual(mockTrack1);
|
|
expect(result.current.currentIndex).toBe(0);
|
|
});
|
|
|
|
it('should resume playback when play is called without track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.pause();
|
|
result.current.play();
|
|
});
|
|
|
|
expect(result.current.isPlaying).toBe(true);
|
|
});
|
|
|
|
it('should add track to queue if not already present', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.play(mockTrack2);
|
|
});
|
|
|
|
expect(result.current.queue).toHaveLength(2);
|
|
expect(result.current.currentTrack).toEqual(mockTrack2);
|
|
expect(result.current.currentIndex).toBe(1);
|
|
});
|
|
|
|
it('should switch to track if already in queue', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.play(mockTrack1);
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack1);
|
|
expect(result.current.currentIndex).toBe(0);
|
|
expect(result.current.queue).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('Pause and resume', () => {
|
|
it('should pause playback', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.pause();
|
|
});
|
|
|
|
expect(result.current.isPlaying).toBe(false);
|
|
});
|
|
|
|
it('should resume playback', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.pause();
|
|
result.current.resume();
|
|
});
|
|
|
|
expect(result.current.isPlaying).toBe(true);
|
|
});
|
|
|
|
it('should stop playback', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.setCurrentTime(50);
|
|
result.current.stop();
|
|
});
|
|
|
|
expect(result.current.isPlaying).toBe(false);
|
|
expect(result.current.currentTime).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Navigation', () => {
|
|
it('should go to next track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2, mockTrack3]);
|
|
result.current.play(mockTrack1);
|
|
result.current.next();
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack2);
|
|
expect(result.current.currentIndex).toBe(1);
|
|
expect(result.current.currentTime).toBe(0);
|
|
});
|
|
|
|
it('should go to previous track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.play(mockTrack2);
|
|
result.current.previous();
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack1);
|
|
expect(result.current.currentIndex).toBe(0);
|
|
});
|
|
|
|
it('should not go to previous if at first track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
const initialIndex = result.current.currentIndex;
|
|
result.current.previous();
|
|
});
|
|
|
|
expect(result.current.currentIndex).toBe(0);
|
|
});
|
|
|
|
it('should loop playlist when repeat is enabled', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.setRepeat('playlist');
|
|
result.current.play(mockTrack2);
|
|
result.current.next();
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack1);
|
|
expect(result.current.currentIndex).toBe(0);
|
|
});
|
|
|
|
it('should not go to next if at end and repeat is off', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.setRepeat('off');
|
|
result.current.play(mockTrack2);
|
|
result.current.next();
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack2);
|
|
expect(result.current.currentIndex).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Seek and time', () => {
|
|
it('should set current time', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setDuration(180);
|
|
result.current.setCurrentTime(50);
|
|
});
|
|
|
|
expect(result.current.currentTime).toBe(50);
|
|
});
|
|
|
|
it('should clamp current time to duration', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setDuration(180);
|
|
result.current.setCurrentTime(200);
|
|
});
|
|
|
|
expect(result.current.currentTime).toBe(180);
|
|
});
|
|
|
|
it('should not allow negative time', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setCurrentTime(-10);
|
|
});
|
|
|
|
expect(result.current.currentTime).toBe(0);
|
|
});
|
|
|
|
it('should set duration', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setDuration(180);
|
|
});
|
|
|
|
expect(result.current.duration).toBe(180);
|
|
});
|
|
|
|
it('should not allow negative duration', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setDuration(-10);
|
|
});
|
|
|
|
expect(result.current.duration).toBe(0);
|
|
});
|
|
|
|
it('should seek to position', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setDuration(180);
|
|
result.current.seek(90);
|
|
});
|
|
|
|
expect(result.current.currentTime).toBe(90);
|
|
});
|
|
});
|
|
|
|
describe('Volume control', () => {
|
|
it('should set volume', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setVolume(50);
|
|
});
|
|
|
|
expect(result.current.volume).toBe(50);
|
|
});
|
|
|
|
it('should clamp volume to 0-100', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setVolume(150);
|
|
});
|
|
|
|
expect(result.current.volume).toBe(100);
|
|
|
|
act(() => {
|
|
result.current.setVolume(-10);
|
|
});
|
|
|
|
expect(result.current.volume).toBe(0);
|
|
});
|
|
|
|
it('should toggle mute', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
expect(result.current.muted).toBe(false);
|
|
|
|
act(() => {
|
|
result.current.toggleMute();
|
|
});
|
|
|
|
expect(result.current.muted).toBe(true);
|
|
|
|
act(() => {
|
|
result.current.toggleMute();
|
|
});
|
|
|
|
expect(result.current.muted).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Repeat and shuffle', () => {
|
|
it('should toggle shuffle', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
expect(result.current.shuffle).toBe(false);
|
|
|
|
act(() => {
|
|
result.current.toggleShuffle();
|
|
});
|
|
|
|
expect(result.current.shuffle).toBe(true);
|
|
});
|
|
|
|
it('should set repeat mode', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.setRepeat('track');
|
|
});
|
|
|
|
expect(result.current.repeat).toBe('track');
|
|
|
|
act(() => {
|
|
result.current.setRepeat('playlist');
|
|
});
|
|
|
|
expect(result.current.repeat).toBe('playlist');
|
|
});
|
|
});
|
|
|
|
describe('Queue management', () => {
|
|
it('should add tracks to queue', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
});
|
|
|
|
expect(result.current.queue).toHaveLength(2);
|
|
expect(result.current.queue).toContainEqual(mockTrack1);
|
|
expect(result.current.queue).toContainEqual(mockTrack2);
|
|
});
|
|
|
|
it('should remove track from queue', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2, mockTrack3]);
|
|
result.current.removeFromQueue(1);
|
|
});
|
|
|
|
expect(result.current.queue).toHaveLength(2);
|
|
expect(result.current.queue).not.toContainEqual(mockTrack2);
|
|
});
|
|
|
|
it('should adjust current index when removing track before current', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2, mockTrack3]);
|
|
result.current.play(mockTrack2);
|
|
result.current.removeFromQueue(0);
|
|
});
|
|
|
|
expect(result.current.currentIndex).toBe(0);
|
|
expect(result.current.currentTrack).toEqual(mockTrack2);
|
|
});
|
|
|
|
it('should handle removing current track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.play(mockTrack1);
|
|
result.current.removeFromQueue(0);
|
|
});
|
|
|
|
expect(result.current.currentTrack).toEqual(mockTrack2);
|
|
expect(result.current.currentIndex).toBe(0);
|
|
});
|
|
|
|
it('should stop playback when removing last track', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.play(mockTrack1);
|
|
result.current.removeFromQueue(0);
|
|
});
|
|
|
|
expect(result.current.currentTrack).toBeNull();
|
|
expect(result.current.isPlaying).toBe(false);
|
|
expect(result.current.currentIndex).toBe(-1);
|
|
});
|
|
|
|
it('should reorder queue', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2, mockTrack3]);
|
|
result.current.reorderQueue(0, 2);
|
|
});
|
|
|
|
expect(result.current.queue[0]).toEqual(mockTrack2);
|
|
expect(result.current.queue[2]).toEqual(mockTrack1);
|
|
});
|
|
|
|
it('should adjust current index when reordering', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2, mockTrack3]);
|
|
result.current.play(mockTrack1);
|
|
result.current.reorderQueue(0, 2);
|
|
});
|
|
|
|
expect(result.current.currentIndex).toBe(2);
|
|
expect(result.current.currentTrack).toEqual(mockTrack1);
|
|
});
|
|
|
|
it('should clear queue', () => {
|
|
const { result } = renderHook(() => usePlayerStore());
|
|
|
|
act(() => {
|
|
result.current.addToQueue([mockTrack1, mockTrack2]);
|
|
result.current.play(mockTrack1);
|
|
result.current.clearQueue();
|
|
});
|
|
|
|
expect(result.current.queue).toHaveLength(0);
|
|
expect(result.current.currentTrack).toBeNull();
|
|
expect(result.current.isPlaying).toBe(false);
|
|
expect(result.current.currentIndex).toBe(-1);
|
|
expect(result.current.currentTime).toBe(0);
|
|
});
|
|
});
|
|
});
|