AudioForge / frontend /src /components /playback-verification.test.tsx
AudioForge Deploy
chore: pre-deployment polish & fixes
5bf2d26
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, act } from '@testing-library/react';
import { GenerationCard } from './generation-card';
import type { GenerationResponse } from '@/lib/api';
// Mock dependencies
vi.mock('@/hooks/use-websocket', () => ({
useGenerationWebSocket: () => ({ lastMessage: null })
}));
interface SliderMockProps {
value: number[];
onValueChange: (v: number[]) => void;
className?: string;
}
vi.mock('@/components/ui/slider', () => ({
Slider: ({ value, onValueChange, className, ...props }: SliderMockProps) => (
<input
type="range"
data-testid="mock-slider"
value={value[0]}
onChange={(e) => onValueChange([parseFloat(e.target.value)])}
className={className}
{...props}
/>
)
}));
// Mock Audio element
const mockPlay = vi.fn().mockResolvedValue(undefined);
const mockPause = vi.fn();
describe('User Story Verification: Playback Control', () => {
const originalPlay = window.HTMLMediaElement.prototype.play;
const originalPause = window.HTMLMediaElement.prototype.pause;
beforeEach(() => {
window.HTMLMediaElement.prototype.play = mockPlay;
window.HTMLMediaElement.prototype.pause = mockPause;
});
afterEach(() => {
window.HTMLMediaElement.prototype.play = originalPlay;
window.HTMLMediaElement.prototype.pause = originalPause;
vi.clearAllMocks();
});
const mockGeneration: GenerationResponse = {
id: 'test-gen-123',
status: 'completed',
prompt: 'A test track',
audio_path: '/music/test.wav',
created_at: new Date().toISOString(),
metadata: {
prompt: 'A test track',
analysis: {
style: 'lo-fi',
tempo: 90,
mood: 'chill'
}
}
};
it('Scenario: User initiates playback and updates UI', async () => {
render(<GenerationCard generation={mockGeneration} />);
// 1. Validate Initial State
// "play" button should be visible
const playButton = screen.getByLabelText('Play');
expect(playButton).toBeInTheDocument();
// "pause" button should NOT be visible
expect(screen.queryByLabelText('Pause')).not.toBeInTheDocument();
// Card should NOT have the highlight class (checking specifically for the ring-primary class)
const card = screen.getByText('A test track').closest('.bg-card');
expect(card).not.toHaveClass('ring-primary');
// 2. Action: Press "play" button
await act(async () => {
fireEvent.click(playButton);
});
// 3. Verify Functionality: Initiate playback
expect(mockPlay).toHaveBeenCalledTimes(1);
// 4. Validate UI Rendering: Update to reflect playing state
// "pause" button SHOULD be visible/enabled
const pauseButton = screen.getByLabelText('Pause');
expect(pauseButton).toBeInTheDocument();
// "play" button should NOT be visible
expect(screen.queryByLabelText('Play')).not.toBeInTheDocument();
// 5. Validate UI Rendering: Visual Highlight
// Card SHOULD have the highlight class
expect(card).toHaveClass('ring-primary');
expect(card).toHaveClass('border-primary');
expect(card).toHaveClass('shadow-[0_0_15px_rgba(var(--primary),0.2)]');
// 6. Action: Press "pause" button
await act(async () => {
fireEvent.click(pauseButton);
});
// 7. Verify Functionality: Pause playback
expect(mockPause).toHaveBeenCalledTimes(1);
// 8. Validate UI Rendering: Return to paused state
expect(screen.getByLabelText('Play')).toBeInTheDocument();
expect(card).not.toHaveClass('ring-primary');
});
});