import { render, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { Track } from '../types'; import { usePlaybackStore } from '../store/usePlaybackStore'; import { useVibeStore } from '../store/useVibeStore'; const { advancePastUnplayableVibeTrack, reportVibeEvent } = vi.hoisted(() => ({ advancePastUnplayableVibeTrack: vi.fn().mockResolvedValue(undefined), reportVibeEvent: vi.fn().mockResolvedValue(undefined), })); vi.mock('../services/vibeSession', () => ({ advancePastUnplayableVibeTrack, reportVibeEvent })); import { AudioEngine } from './AudioEngine'; const track = (id: string): Track => ({ id, path: `/music/${id}.mp3`, hash: id, title: id, artist: 'Artist', album_id: 'album', duration: 180, state: 'LIBRARY', source_type: 'MANUAL', play_count: 0, skip_count: 0, dislike_count: 0, }); describe('AudioEngine', () => { beforeEach(() => { vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => undefined); vi.spyOn(HTMLMediaElement.prototype, 'play').mockResolvedValue(undefined); useVibeStore.getState().reset(); useVibeStore.getState().setActiveSession({ sessionId: 'session-a', seedTrackId: 'song' }); usePlaybackStore.setState({ currentTrack: track('song'), queue: [track('song')], currentIndex: 0, isPlaying: false, queueOwner: 'vibe', vibeAdvanceHandler: () => undefined, }); }); afterEach(() => vi.restoreAllMocks()); it('uses the durable unplayable advancement when a Vibe stream errors after metadata resolved', async () => { const { container } = render(); const audio = container.querySelector('audio')!; audio.dispatchEvent(new Event('error')); audio.dispatchEvent(new Event('error')); await waitFor(() => expect(advancePastUnplayableVibeTrack).toHaveBeenCalledWith('song')); expect(advancePastUnplayableVibeTrack).toHaveBeenCalledTimes(1); expect(reportVibeEvent).not.toHaveBeenCalledWith('skipped', 'song'); }); });