import { describe, expect, it } from '@ohos/hypium' import { PlaybackCoordinator, PlaybackRuntime } from '../../../main/ets/controller/PlaybackCoordinator' import { PlaybackStateBridge } from '../../../main/ets/controller/PlaybackStateBridge' import { VideoItem } from '../../../main/ets/viewmodel/VideoItem' export default function playbackCoordinatorTest() { describe('PlaybackCoordinatorTest', () => { it('playQueueCallsRuntimeAndUpdatesState', 0, async () => { const bridge = PlaybackStateBridge.getInstance() const coordinator = PlaybackCoordinator.getInstance() const calls: string[] = [] const songs: VideoItem[] = [ new VideoItem('Song A', '1', '/music/a.flac', 0, 0, '2026-04-02'), new VideoItem('Song B', '2', '/music/b.flac', 0, 0, '2026-04-02') ] const runtime: PlaybackRuntime = { playQueue: async (_queue, startIndex, source) => { calls.push(`playQueue:${startIndex}:${source}`) }, playOrPause: async () => { calls.push('playOrPause') }, playNext: async () => { calls.push('playNext') }, playPrevious: async () => { calls.push('playPrevious') }, seekTo: async (value, source) => { calls.push(`seekTo:${value}:${source ?? ''}`) } } bridge.reset() coordinator.clearRuntime() coordinator.setRuntime(runtime) await coordinator.playQueue(songs, 1, 'charts-count') const snapshot = bridge.getSnapshot() expect(snapshot.currentIndex).assertEqual(1) expect(snapshot.currentSong?.filePath ?? '').assertEqual('/music/b.flac') expect(calls.join(',')).assertEqual('playQueue:1:charts-count') coordinator.clearRuntime() }) it('playSongPropagatesStartIndexAndSourceAndUpdatesBridge', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() const bridge = PlaybackStateBridge.getInstance() const song = new VideoItem('Song C', '3', '/music/c.flac', 0, 0, '2026-04-02') const runtimeCalls: string[] = [] bridge.reset() coordinator.clearRuntime() coordinator.setRuntime({ playQueue: async (_queue, startIndex, source) => { runtimeCalls.push(`${startIndex}:${source}`) }, playOrPause: async () => {}, playNext: async () => {}, playPrevious: async () => {}, seekTo: async () => {} }) await coordinator.playSong(song, 'playlist-detail') const snapshot = bridge.getSnapshot() expect(runtimeCalls.join(',')).assertEqual('0:playlist-detail') expect(snapshot.currentIndex).assertEqual(0) expect(snapshot.currentSong?.filePath ?? '').assertEqual('/music/c.flac') coordinator.clearRuntime() }) it('clearRuntimeOnlyRemovesMatchingInstance', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() let playOrPauseCount = 0 const runtimeA: PlaybackRuntime = { playQueue: async () => {}, playOrPause: async () => { playOrPauseCount++ }, playNext: async () => {}, playPrevious: async () => {}, seekTo: async () => {} } const runtimeB: PlaybackRuntime = { playQueue: async () => {}, playOrPause: async () => {}, playNext: async () => {}, playPrevious: async () => {}, seekTo: async () => {} } coordinator.clearRuntime() coordinator.setRuntime(runtimeA) coordinator.clearRuntime(runtimeB) await coordinator.playOrPause() expect(playOrPauseCount).assertEqual(1) coordinator.clearRuntime(runtimeA) await coordinator.playOrPause() expect(playOrPauseCount).assertEqual(1) }) it('coordinatorTransportControlsUseRegisteredRuntimeOnly', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() const calls: string[] = [] coordinator.clearRuntime() coordinator.setRuntime({ playQueue: async () => {}, playOrPause: async () => { calls.push('playOrPause') }, playNext: async () => { calls.push('playNext') }, playPrevious: async () => { calls.push('playPrevious') }, seekTo: async (value, source) => { calls.push(`seekTo:${value}:${source ?? ''}`) } }) await coordinator.playOrPause() await coordinator.playNext() await coordinator.playPrevious() await coordinator.seekTo('1200', 'slider') expect(calls.join(',')).assertEqual('playOrPause,playNext,playPrevious,seekTo:1200:slider') coordinator.clearRuntime() }) it('playQueueWithoutRuntimeKeepsBridgeState', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() const bridge = PlaybackStateBridge.getInstance() const initialSong = new VideoItem('Initial', 'init', '/music/initial.flac', 0, 0, '2026-04-02') bridge.reset() bridge.replaceQueue([initialSong], 0) const before = bridge.getSnapshot() coordinator.clearRuntime() await coordinator.playQueue( [new VideoItem('Song D', '4', '/music/d.flac', 0, 0, '2026-04-02')], 0, 'charts' ) const after = bridge.getSnapshot() expect(after.currentIndex).assertEqual(before.currentIndex) expect(after.currentSong?.filePath ?? '').assertEqual(before.currentSong?.filePath ?? '') expect(after.queue.length).assertEqual(before.queue.length) }) it('playQueueRollsBackOnRuntimeError', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() const bridge = PlaybackStateBridge.getInstance() const previousSong = new VideoItem('Previous', 'prev', '/music/prev.flac', 0, 0, '2026-04-02') bridge.reset() bridge.replaceQueue([previousSong], 0) const snapshotBefore = bridge.getSnapshot() let threwError = false coordinator.clearRuntime() coordinator.setRuntime({ playQueue: async () => { throw new Error('boom') }, playOrPause: async () => {}, playNext: async () => {}, playPrevious: async () => {}, seekTo: async () => {} }) try { await coordinator.playQueue( [ new VideoItem('Song E', '5', '/music/e.flac', 0, 0, '2026-04-02'), new VideoItem('Song F', '6', '/music/f.flac', 0, 0, '2026-04-02') ], 0, 'playlist' ) } catch (error) { threwError = true } expect(threwError).assertEqual(true) const snapshotAfter = bridge.getSnapshot() expect(snapshotAfter.queue.length).assertEqual(snapshotBefore.queue.length) expect(snapshotAfter.currentIndex).assertEqual(snapshotBefore.currentIndex) expect(snapshotAfter.currentSong?.filePath ?? '').assertEqual(snapshotBefore.currentSong?.filePath ?? '') coordinator.clearRuntime() }) it('playSongWrapsSingleSongQueue', 0, async () => { const coordinator = PlaybackCoordinator.getInstance() let queueLength = 0 coordinator.clearRuntime() coordinator.setRuntime({ playQueue: async (queue) => { queueLength = queue.length }, playOrPause: async () => {}, playNext: async () => {}, playPrevious: async () => {}, seekTo: async () => {} }) await coordinator.playSong(new VideoItem('Song A', '1', '/music/a.flac', 0, 0, '2026-04-02'), 'playlist-detail') expect(queueLength).assertEqual(1) coordinator.clearRuntime() }) }) }