mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
fix: align promise helper types and test playback rejections
This commit is contained in:
1 parent
0f47e11d63
commit
72cf3551e2
13 files changed
+191
-44
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { Buffer } from 'node:buffer'
|
||||
// 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 { fileURLToPath } from 'node:url'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
// Bundle the source modules so tests also work with Node 20's ESM resolution.
|
||||
const { outputFiles } = await build({
|
||||
stdin: {
|
||||
contents: `
|
||||
export { silencePromise } from './packages/artplayer/src/utils/error.js';
|
||||
export { default as Emitter } from './packages/artplayer/src/utils/emitter.js';
|
||||
export { default as Hotkey } from './packages/artplayer/src/hotkey.js';
|
||||
export { default as playMix } from './packages/artplayer/src/player/playMix.js';
|
||||
export { default as pauseMix } from './packages/artplayer/src/player/pauseMix.js';
|
||||
export { default as toggleMix } from './packages/artplayer/src/player/toggleMix.js';
|
||||
export { default as switchMix } from './packages/artplayer/src/player/switchMix.js';
|
||||
`,
|
||||
resolveDir: fileURLToPath(new URL('../', import.meta.url)),
|
||||
},
|
||||
bundle: true,
|
||||
write: false,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
})
|
||||
const { silencePromise, Emitter, Hotkey, playMix, pauseMix, toggleMix, switchMix } = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(outputFiles[0].contents).toString('base64')}`,
|
||||
)
|
||||
|
||||
function createArt(play = () => Promise.resolve(), playing = false) {
|
||||
const art = Object.assign(new Emitter(), {
|
||||
url: 'old.mp4',
|
||||
currentTime: 37,
|
||||
aspectRatio: '16:9',
|
||||
playbackRate: 1.5,
|
||||
notice: { show: '' },
|
||||
i18n: { get: key => key },
|
||||
option: { hotkey: true, mutex: false },
|
||||
constructor: { instances: [] },
|
||||
template: {
|
||||
$video: {
|
||||
paused: !playing,
|
||||
play: mock.fn(play),
|
||||
pause() { this.paused = true },
|
||||
},
|
||||
},
|
||||
})
|
||||
Object.defineProperty(art, 'playing', { get: () => !art.template.$video.paused })
|
||||
for (const mix of [playMix, pauseMix, toggleMix, switchMix]) {
|
||||
mix(art)
|
||||
}
|
||||
return art
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Utils } from '../packages/artplayer/types/utils'
|
||||
|
||||
export function checkSilencePromiseTypes(utils: Utils, thenable: PromiseLike<number>) {
|
||||
const promise: Promise<number | undefined> = utils.silencePromise(Promise.resolve(42))
|
||||
const synchronous: undefined = utils.silencePromise(undefined)
|
||||
const unchanged: PromiseLike<number> = utils.silencePromise(thenable)
|
||||
// @ts-expect-error A catch-less thenable is not converted to a native Promise.
|
||||
utils.silencePromise(thenable).catch(() => {})
|
||||
return { promise, synchronous, unchanged }
|
||||
}
|
||||
Reference in new issue
Block a user