import assert from 'node:assert/strict'; import test from 'node:test'; import { MediaController } from '../vr180player/media/media-controller.js'; function createClassList() { const classes = new Set(); return { add: (className) => classes.add(className), contains: (className) => classes.has(className), remove: (className) => classes.delete(className) }; } function createVideo(overrides = {}) { const video = { HAVE_ENOUGH_DATA: 4, controls: true, currentSrc: 'video.mp4', currentTime: 20, duration: 120, ended: false, loop: false, loadCount: 0, muted: false, pauseCount: 0, paused: true, playCount: 0, readyState: 4, volume: 1, load() { this.loadCount += 1; }, pause() { this.pauseCount += 1; this.paused = true; }, play() { this.playCount += 1; this.paused = false; this.ended = false; return Promise.resolve(); }, ...overrides }; return video; } function createController({ is2DModeActive = () => false, on2DPlaybackResume = () => {}, playButton = { classList: createClassList(), disabled: true }, video = createVideo() } = {}) { return { controller: new MediaController({ is2DModeActive, on2DPlaybackResume, playButton, video }), playButton, video }; } test('MediaController clamps skip controls to the video bounds', () => { const { controller, video } = createController(); controller.forward(); assert.equal(video.currentTime, 35); video.currentTime = 118; controller.forward(); assert.equal(video.currentTime, 120); controller.rewind(); assert.equal(video.currentTime, 105); video.currentTime = 4; controller.rewind(); assert.equal(video.currentTime, 0); }); test('MediaController seeks by progress and ignores videos without finite duration', () => { const { controller, video } = createController(); controller.seekToProgress(0.25); assert.equal(video.currentTime, 30); video.duration = Number.POSITIVE_INFINITY; controller.seekToProgress(0.75); assert.equal(video.currentTime, 30); }); test('MediaController toggles mute and native controls', () => { const { controller, video } = createController(); controller.toggleMute(); assert.equal(video.muted, true); controller.toggleMute(); assert.equal(video.muted, false); video.controls = false; controller.enableNativeControls(); assert.equal(video.controls, true); }); test('MediaController toggles loop playback state', () => { const { controller, video } = createController(); assert.equal(controller.isLooping(), false); assert.equal(controller.toggleLoop(), true); assert.equal(video.loop, true); assert.equal(controller.isLooping(), true); assert.equal(controller.toggleLoop(), false); assert.equal(video.loop, false); }); test('MediaController resets video and play button to poster state', () => { const playButton = { classList: createClassList(), disabled: true }; playButton.classList.add('hidden'); const { controller, video } = createController({ playButton }); controller.resetToOriginalState(); assert.equal(video.pauseCount, 1); assert.equal(video.currentTime, 0); assert.equal(video.controls, false); assert.equal(video.loadCount, 1); assert.equal(playButton.classList.contains('hidden'), false); assert.equal(playButton.disabled, false); }); test('MediaController restarts ended video before playing again', async () => { let resumed = false; const { controller, video } = createController({ is2DModeActive: () => true, on2DPlaybackResume: () => { resumed = true; }, video: createVideo({ currentTime: 120, ended: true, paused: true }) }); controller.togglePlayPause(); await Promise.resolve(); assert.equal(video.currentTime, 0); assert.equal(video.playCount, 1); assert.equal(resumed, true); const vrVideo = createVideo({ currentTime: 120, ended: true, paused: true }); const { controller: vrController } = createController({ video: vrVideo }); vrController.togglePlayPause(); await Promise.resolve(); assert.equal(vrVideo.currentTime, 0); assert.equal(vrVideo.playCount, 1); }); test('MediaController pauses when toggling playback while already playing', () => { const { controller, video } = createController({ video: createVideo({ paused: false }) }); controller.togglePlayPause(); assert.equal(video.pauseCount, 1); assert.equal(video.paused, true); }); test('MediaController dispatches ended behavior for VR, 2D, and idle modes', () => { const vrCalls = []; const { controller } = createController({ video: createVideo({ paused: false }) }); controller.handleEnded({ isIn2DMode: () => false, isInVr: () => true, on2DEnded: () => vrCalls.push('2d'), onVrEnded: () => vrCalls.push('vr'), resetToOriginalState: () => vrCalls.push('reset') }); assert.deepEqual(vrCalls, ['vr']); const twoDCalls = []; controller.handleEnded({ isIn2DMode: () => true, isInVr: () => false, on2DEnded: () => twoDCalls.push('2d'), onVrEnded: () => twoDCalls.push('vr'), resetToOriginalState: () => twoDCalls.push('reset') }); assert.deepEqual(twoDCalls, ['2d']); const idleCalls = []; controller.handleEnded({ isIn2DMode: () => false, isInVr: () => false, on2DEnded: () => idleCalls.push('2d'), onVrEnded: () => idleCalls.push('vr'), resetToOriginalState: () => idleCalls.push('reset') }); assert.deepEqual(idleCalls, ['reset']); });