Files
ArtPlayer/test/playback.test.js
T

98 lines
4.0 KiB
JavaScript

import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Use the built-in runner without adding a test framework.
import { mock, test } from 'node:test'
import { setImmediate } from 'node:timers/promises'
import { loadModules } from './helpers/load.js'
import { playbackFactory } from './helpers/playback.js'
const modules = await loadModules({
silencePromise: { file: 'packages/artplayer/src/utils/error', name: 'silencePromise' },
Emitter: 'packages/artplayer/src/utils/emitter',
Hotkey: 'packages/artplayer/src/hotkey',
playMix: 'packages/artplayer/src/player/playMix',
pauseMix: 'packages/artplayer/src/player/pauseMix',
toggleMix: 'packages/artplayer/src/player/toggleMix',
switchMix: 'packages/artplayer/src/player/switchMix',
})
const { silencePromise, Hotkey } = modules
const createArt = playbackFactory(modules)
test('silencePromise preserves fulfilled values and handles rejected promises', async () => {
assert.equal(await silencePromise(Promise.resolve(42)), 42)
assert.equal(await silencePromise(Promise.reject(new Error('interrupted'))), undefined)
})
test('silencePromise passes through synchronous values and catch-less thenables', () => {
const thenable = { then: (resolve, reject) => Promise.resolve(42).then(resolve, reject) }
for (const value of [undefined, null, false, 42, thenable]) {
assert.equal(silencePromise(value), value)
}
assert.equal(createArt(undefined, true).toggle(), undefined)
})
for (const name of ['AbortError', 'NotAllowedError', 'NotSupportedError']) {
test(`public play/toggle still reject with ${name}`, async () => {
const error = new DOMException('Playback failed', name)
const art = createArt(() => Promise.reject(error))
const onPlay = mock.fn()
art.on('play', onPlay)
await assert.rejects(art.play(), actual => actual === error)
await assert.rejects(art.toggle(), actual => actual === error)
assert.equal(onPlay.mock.callCount(), 0)
})
for (const method of ['switchUrl', 'switchQuality']) {
test(`${method} settles when resume rejects with ${name}`, { timeout: 1000 }, async () => {
const art = createArt(() => Promise.reject(new DOMException('Playback failed', name)), true)
const switching = art[method]('new.mp4')
art.currentTime = 0
art.playbackRate = 1
art.aspectRatio = 'default'
art.emit('video:loadedmetadata')
art.emit('video:canplay')
await switching
assert.equal(art.currentTime, method === 'switchQuality' ? 37 : 0)
assert.equal(art.playbackRate, 1.5)
assert.equal(art.aspectRatio, '16:9')
assert.equal(art.notice.show, '')
assert.equal(art.template.$video.play.mock.callCount(), 1)
assert.deepEqual(Object.keys(art.e), [])
})
}
}
test('Space handles an interrupted pending play without an unhandled rejection', async () => {
let rejectPlay
const art = createArt(() => new Promise((resolve, reject) => {
rejectPlay = reject
}))
const hotkey = new Hotkey(art)
hotkey.keys.Space[0]()
art.pause()
rejectPlay(new DOMException('Interrupted by pause', 'AbortError'))
// node:test fails on unhandled rejections, including those from event callbacks.
await setImmediate()
assert.equal(art.playing, false)
})
test('switch resumes successfully and remains paused when previously paused', async () => {
for (const playing of [true, false]) {
const art = createArt(undefined, playing)
const switching = art.switchUrl('new.mp4')
art.emit('video:loadedmetadata')
art.emit('video:canplay')
await switching
assert.equal(art.template.$video.play.mock.callCount(), playing ? 1 : 0)
}
})
test('source errors still reject switches and clean up listeners', async () => {
const art = createArt()
const error = new Error('Source failed to load')
const rejected = assert.rejects(art.switchUrl('bad.mp4'), actual => actual === error)
art.emit('video:error', error)
await rejected
assert.deepEqual(Object.keys(art.e), [])
assert.equal(art.template.$video.play.mock.callCount(), 0)
})