mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
176 lines
6.3 KiB
JavaScript
176 lines
6.3 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Use the repository Node test runner.
|
|
import { test } from 'node:test'
|
|
import { loadModules } from './helpers/load.js'
|
|
|
|
const names = ['currentTimeMix', 'durationMix', 'seekMix', 'volumeMix', 'playbackRateMix', 'playedMix', 'loadedMix', 'stateMix', 'toggleMix']
|
|
const mixins = await loadModules(Object.fromEntries(names.map(name => [name, `packages/artplayer/src/player/${name}`])))
|
|
|
|
function createArt() {
|
|
const stored = []
|
|
const emitted = []
|
|
const video = { currentTime: 0, duration: 100, volume: 0.5, muted: false, playbackRate: 1, buffered: { length: 0, end() {
|
|
throw new Error('empty')
|
|
} } }
|
|
const art = {
|
|
template: { $video: video },
|
|
notice: { show: '' },
|
|
i18n: { get: key => key },
|
|
storage: { set: (...args) => stored.push(args) },
|
|
emit: (...args) => emitted.push(args),
|
|
mini: false,
|
|
pip: false,
|
|
fullscreen: false,
|
|
fullscreenWeb: false,
|
|
}
|
|
for (const name of names.filter(name => name !== 'toggleMix'))
|
|
mixins[name](art)
|
|
return { art, video, stored, emitted }
|
|
}
|
|
|
|
test('currentTime keeps parseFloat coercion, clamps and ignores NaN', () => {
|
|
const { art, video } = createArt()
|
|
for (const [input, expected] of [['12.5 seconds', 12.5], [-2, 0], [200, 100], [Infinity, 100], [-Infinity, 0]]) {
|
|
art.currentTime = input
|
|
assert.equal(video.currentTime, expected)
|
|
}
|
|
art.currentTime = 7
|
|
for (const input of [Number.NaN, undefined, null, '', 'invalid']) {
|
|
art.currentTime = input
|
|
assert.equal(art.currentTime, 7)
|
|
}
|
|
let coerced = 0
|
|
art.currentTime = { toString() {
|
|
coerced++
|
|
return '22s'
|
|
} }
|
|
assert.equal(art.currentTime, 22)
|
|
assert.equal(coerced, 1)
|
|
assert.throws(() => art.currentTime = Symbol('time'), TypeError)
|
|
})
|
|
|
|
test('seek preserves original payload, notification order and forward/backward coercion', () => {
|
|
const { art, emitted } = createArt()
|
|
art.seek = '12.5 seconds'
|
|
assert.equal(art.currentTime, 12.5)
|
|
assert.equal(art.notice.show, '00:12 / 01:40')
|
|
assert.deepEqual(emitted.at(-1), ['seek', 12.5, '12.5 seconds'])
|
|
art.currentTime = 10
|
|
art.forward = '2'
|
|
assert.deepEqual(emitted.at(-1), ['seek', 100, '102'])
|
|
art.backward = '2'
|
|
assert.deepEqual(emitted.at(-1), ['seek', 98, 98])
|
|
for (const name of ['seek', 'forward', 'backward']) {
|
|
assert.equal(art[name], undefined)
|
|
const descriptor = Object.getOwnPropertyDescriptor(art, name)
|
|
assert.equal(descriptor.get, undefined)
|
|
assert.equal(descriptor.enumerable, false)
|
|
assert.equal(descriptor.configurable, false)
|
|
}
|
|
})
|
|
|
|
test('volume uses actual assigned media volume, historical percentage formatting and nonzero persistence', () => {
|
|
const { art, video, stored, emitted } = createArt()
|
|
art.volume = 2
|
|
assert.equal(video.volume, 1)
|
|
assert.equal(art.notice.show, 'Volume: 100')
|
|
art.volume = -1
|
|
assert.equal(video.volume, 0)
|
|
assert.deepEqual(stored, [['volume', 1]])
|
|
art.volume = 1e-9
|
|
assert.equal(art.notice.show, 'Volume: 1')
|
|
assert.deepEqual(stored.at(-1), ['volume', 1e-9])
|
|
art.muted = 'legacy-truthy'
|
|
assert.equal(video.muted, 'legacy-truthy')
|
|
assert.deepEqual(emitted.at(-1), ['muted', 'legacy-truthy'])
|
|
})
|
|
|
|
test('playbackRate preserves equal-value no-op, falsy normalization and native errors', () => {
|
|
const { art, video } = createArt()
|
|
art.notice.show = 'keep'
|
|
art.playbackRate = 1
|
|
assert.equal(art.notice.show, 'keep')
|
|
art.playbackRate = 1.5
|
|
assert.equal(art.notice.show, 'Rate: 1.5x')
|
|
art.playbackRate = 0
|
|
assert.equal(video.playbackRate, 1)
|
|
assert.equal(art.notice.show, 'Rate: Normal')
|
|
art.notice.show = 'keep again'
|
|
art.playbackRate = Number.NaN
|
|
assert.equal(art.notice.show, 'keep again')
|
|
const error = new Error('native rate rejected')
|
|
Object.defineProperty(video, 'playbackRate', { get: () => 1, set() {
|
|
throw error
|
|
} })
|
|
assert.throws(() => art.playbackRate = -1, actual => actual === error)
|
|
})
|
|
|
|
test('progress getters keep raw ratios and the last buffered range', () => {
|
|
const { art, video } = createArt()
|
|
assert.equal(art.loadedTime, 0)
|
|
video.buffered = { length: 2, end: index => [3, 40][index] }
|
|
assert.equal(art.loadedTime, 40)
|
|
assert.equal(art.loaded, 0.4)
|
|
art.currentTime = 25
|
|
assert.equal(art.played, 0.25)
|
|
video.duration = Infinity
|
|
assert.equal(art.duration, 0)
|
|
assert.equal(art.played, Infinity)
|
|
assert.equal(art.loaded, 0)
|
|
video.currentTime = 0
|
|
video.duration = 0
|
|
video.buffered = { length: 0 }
|
|
assert(Number.isNaN(art.played))
|
|
assert(Number.isNaN(art.loaded))
|
|
})
|
|
|
|
test('state prioritizes mini/pip/fullscreen/web and only disables other states', () => {
|
|
const { art } = createArt()
|
|
assert.equal(art.state, 'standard')
|
|
art.mini = art.pip = art.fullscreen = art.fullscreenWeb = true
|
|
assert.equal(art.state, 'mini')
|
|
art.state = 'fullscreen'
|
|
assert.deepEqual([art.mini, art.pip, art.fullscreen, art.fullscreenWeb], [false, false, true, false])
|
|
assert.equal(art.state, 'fullscreen')
|
|
art.state = 'unknown'
|
|
assert.equal(art.state, 'standard')
|
|
art.state = 'pip'
|
|
assert.equal(art.pip, false)
|
|
})
|
|
|
|
test('captured media attributes remain captured while duration follows the current template', () => {
|
|
const { art, video } = createArt()
|
|
art.template.$video = { currentTime: 88, duration: 200, volume: 0.7, muted: true, playbackRate: 2, buffered: { length: 0 } }
|
|
assert.equal(art.duration, 200)
|
|
art.currentTime = 150
|
|
assert.equal(video.currentTime, 150)
|
|
assert.equal(art.template.$video.currentTime, 88)
|
|
assert.equal(art.volume, 0.5)
|
|
assert.equal(art.muted, false)
|
|
assert.equal(art.playbackRate, 1)
|
|
video.buffered = { length: 1, end: () => 50 }
|
|
assert.equal(art.loaded, 0.5)
|
|
assert.equal(art.played, 0.75)
|
|
})
|
|
|
|
test('toggle returns exact dynamic branch results and preserves extracted function binding', async () => {
|
|
const pending = Promise.resolve(42)
|
|
const calls = []
|
|
const art = { playing: false, play() {
|
|
calls.push(this)
|
|
return pending
|
|
}, pause() {
|
|
calls.push(this)
|
|
return 'paused'
|
|
} }
|
|
mixins.toggleMix(art)
|
|
const toggle = art.toggle
|
|
assert.equal(toggle(), pending)
|
|
art.playing = true
|
|
assert.equal(toggle.call({}), 'paused')
|
|
assert.deepEqual(calls, [art, art])
|
|
assert.equal(await pending, 42)
|
|
const descriptor = Object.getOwnPropertyDescriptor(art, 'toggle')
|
|
assert.deepEqual([descriptor.writable, descriptor.configurable, descriptor.enumerable], [false, false, false])
|
|
})
|