Files
ArtPlayer/test/playback-properties.test.js

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])
})