import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock the videoSession store (playback.ts imports it) vi.mock('$lib/stores/videoSession.svelte', () => ({ session: { duration: 120, currentTime: 0, fps: 30, isPlaying: false, keyframePositions: [], }, })); import { setVideoElement, togglePiP, isPiPActive, toggleFullscreen, isFullscreenActive, setPlaybackRate, getPlaybackRate, adjustShuttle, } from '$lib/transport/playback'; function createMockVideoElement(): HTMLVideoElement { const el = document.createElement('video'); // PiP (el as any).requestPictureInPicture = vi.fn().mockResolvedValue({}); // Fullscreen el.requestFullscreen = vi.fn().mockResolvedValue(undefined); return el; } describe('playback — PiP helpers', () => { let mockVideo: HTMLVideoElement; beforeEach(() => { mockVideo = createMockVideoElement(); setVideoElement(mockVideo); // Reset PiP state Object.defineProperty(document, 'pictureInPictureElement', { value: null, writable: true, configurable: true, }); Object.defineProperty(document, 'pictureInPictureEnabled', { value: true, writable: true, configurable: true, }); }); it('isPiPActive returns false when no PiP element', () => { expect(isPiPActive()).toBe(false); }); it('togglePiP calls requestPictureInPicture', () => { togglePiP(); expect((mockVideo as any).requestPictureInPicture).toHaveBeenCalled(); }); it('isPiPActive returns true when PiP element exists', () => { Object.defineProperty(document, 'pictureInPictureElement', { value: mockVideo, writable: true, configurable: true, }); expect(isPiPActive()).toBe(true); }); }); describe('playback — fullscreen helpers', () => { let mockVideo: HTMLVideoElement; beforeEach(() => { mockVideo = createMockVideoElement(); setVideoElement(mockVideo); Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true, configurable: true, }); }); it('isFullscreenActive returns false when not fullscreen', () => { expect(isFullscreenActive()).toBe(false); }); it('toggleFullscreen calls requestFullscreen on document.documentElement', () => { const spy = vi.fn().mockResolvedValue(undefined); document.documentElement.requestFullscreen = spy; toggleFullscreen(); expect(spy).toHaveBeenCalled(); }); it('isFullscreenActive returns true when fullscreen element exists', () => { Object.defineProperty(document, 'fullscreenElement', { value: mockVideo, writable: true, configurable: true, }); expect(isFullscreenActive()).toBe(true); }); }); describe('playback — playback rate helpers', () => { let mockVideo: HTMLVideoElement; beforeEach(() => { mockVideo = createMockVideoElement(); setVideoElement(mockVideo); }); it('getPlaybackRate returns 1 by default', () => { expect(getPlaybackRate()).toBe(1); }); it('setPlaybackRate changes the video playback rate', () => { setPlaybackRate(1.5); expect(mockVideo.playbackRate).toBe(1.5); }); it('getPlaybackRate returns the current rate', () => { setPlaybackRate(2); expect(getPlaybackRate()).toBe(2); }); it('getPlaybackRate returns 1 when no video element', () => { setVideoElement(null); expect(getPlaybackRate()).toBe(1); }); }); describe('playback — adjustShuttle', () => { let mockVideo: HTMLVideoElement; beforeEach(() => { mockVideo = createMockVideoElement(); setVideoElement(mockVideo); mockVideo.playbackRate = 1; Object.defineProperty(mockVideo, 'paused', { value: true, writable: true, configurable: true, }); mockVideo.play = vi.fn().mockResolvedValue(undefined); }); it('increases rate by 0.25 when dir is 1', () => { const result = adjustShuttle(1); expect(result).toBe(1.25); expect(mockVideo.playbackRate).toBe(1.25); }); it('decreases rate by 0.25 when dir is -1', () => { const result = adjustShuttle(-1); expect(result).toBe(0.75); expect(mockVideo.playbackRate).toBe(0.75); }); it('reads current rate from video element, not external state', () => { mockVideo.playbackRate = 0.75; const result = adjustShuttle(1); expect(result).toBe(1); }); it('clamps to minimum 0.25', () => { mockVideo.playbackRate = 0.25; const result = adjustShuttle(-1); expect(result).toBe(0.25); }); it('clamps to maximum 4', () => { mockVideo.playbackRate = 4; const result = adjustShuttle(1); expect(result).toBe(4); }); it('starts playback if paused', () => { adjustShuttle(1); expect(mockVideo.play).toHaveBeenCalled(); }); it('returns 1 when no video element', () => { setVideoElement(null); expect(adjustShuttle(1)).toBe(1); }); });