veza/apps/web/src/features/player/store/playerStore.test.ts
2025-12-12 21:34:34 -05:00

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);
});
});
});