mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
230 lines
6.6 KiB
JavaScript
230 lines
6.6 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 { fastForward, Emitter, beginLifecycle, getScope, beginSource } = await loadModules({
|
|
fastForward: 'packages/artplayer/src/plugins/fastForward',
|
|
Emitter: 'packages/artplayer/src/utils/emitter',
|
|
beginLifecycle: { file: 'packages/artplayer/src/lifecycle/instance', name: 'beginLifecycle' },
|
|
getScope: { file: 'packages/artplayer/src/lifecycle/instance', name: 'getScope' },
|
|
beginSource: { file: 'packages/artplayer/src/source/operation', name: 'beginSource' },
|
|
})
|
|
|
|
function fixture(t) {
|
|
let id = 0
|
|
const timers = new Map()
|
|
t.mock.method(globalThis, 'setTimeout', (callback, delay) => {
|
|
const key = ++id
|
|
timers.set(key, { callback, delay })
|
|
return key
|
|
})
|
|
t.mock.method(globalThis, 'clearTimeout', key => timers.delete(key))
|
|
const classes = new Set()
|
|
const video = new EventTarget()
|
|
const art = Object.assign(new Emitter(), {
|
|
template: { $video: video, $player: { classList: {
|
|
add: value => classes.add(value),
|
|
remove: value => classes.delete(value),
|
|
contains: value => classes.has(value),
|
|
} } },
|
|
constructor: { FAST_FORWARD_TIME: 100, FAST_FORWARD_VALUE: 3 },
|
|
playing: true,
|
|
isLock: false,
|
|
playbackRate: 1.5,
|
|
proxy(target, name, callback) {
|
|
target.addEventListener(name, callback)
|
|
const remove = () => target.removeEventListener(name, callback)
|
|
getScope(art).add(() => {
|
|
remove()
|
|
})
|
|
return remove
|
|
},
|
|
})
|
|
beginLifecycle(art)
|
|
beginSource(art)
|
|
const plugin = fastForward(art)
|
|
const start = (count = 1) => {
|
|
const event = new Event('touchstart')
|
|
Object.defineProperty(event, 'touches', { value: Array.from({ length: count }, (_, identifier) => ({ identifier })) })
|
|
video.dispatchEvent(event)
|
|
}
|
|
const flush = () => {
|
|
const callbacks = [...timers.values()]
|
|
timers.clear()
|
|
for (const { callback } of callbacks)
|
|
callback()
|
|
}
|
|
return { art, plugin, start, flush, timers, video, scope: getScope(art) }
|
|
}
|
|
|
|
test('fastForward preserves name, state, live constants and previous playback rate', (t) => {
|
|
const f = fixture(t)
|
|
assert.deepEqual(Object.keys(f.plugin), ['name', 'state'])
|
|
assert.equal(f.plugin.name, 'fastForward')
|
|
assert.equal(f.plugin.state, false)
|
|
f.art.constructor.FAST_FORWARD_TIME = 200
|
|
f.start()
|
|
assert.equal([...f.timers.values()][0].delay, 200)
|
|
f.art.constructor.FAST_FORWARD_VALUE = 4
|
|
f.flush()
|
|
assert.equal(f.art.playbackRate, 4)
|
|
assert.equal(f.plugin.state, true)
|
|
f.art.emit('document:touchend', new Event('touchend'))
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.scope.dispose()
|
|
})
|
|
|
|
test('fastForward only schedules a single playing unlocked touch', (t) => {
|
|
const f = fixture(t)
|
|
f.start(2)
|
|
f.art.playing = false
|
|
f.start()
|
|
f.art.playing = true
|
|
f.art.isLock = true
|
|
f.start()
|
|
assert.equal(f.timers.size, 0)
|
|
f.scope.dispose()
|
|
})
|
|
|
|
test('repeated touch starts do not leave timers or overwrite the original rate', (t) => {
|
|
const f = fixture(t)
|
|
f.start()
|
|
f.start()
|
|
assert.equal(f.timers.size, 1)
|
|
f.flush()
|
|
f.start(2)
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.scope.dispose()
|
|
})
|
|
|
|
for (const event of ['document:touchmove', 'document:touchend', 'document:touchcancel', 'video:pause', 'destroy']) {
|
|
test(`fastForward cancels pending and active presses on ${event}`, (t) => {
|
|
const f = fixture(t)
|
|
f.start()
|
|
const stale = [...f.timers.values()][0].callback
|
|
f.art.emit(event, new Event(event))
|
|
assert.equal(f.timers.size, 0)
|
|
stale()
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
f.start()
|
|
f.flush()
|
|
assert.equal(f.plugin.state, true)
|
|
f.art.emit(event, new Event(event))
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.scope.dispose()
|
|
})
|
|
}
|
|
|
|
test('native touchcancel, lock, source replacement and destruction release the press', (t) => {
|
|
const f = fixture(t)
|
|
f.start()
|
|
f.video.dispatchEvent(new Event('touchcancel'))
|
|
assert.equal(f.timers.size, 0)
|
|
f.start()
|
|
f.flush()
|
|
f.art.isLock = true
|
|
f.art.emit('lock', true)
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
f.art.isLock = false
|
|
f.start()
|
|
f.flush()
|
|
beginSource(f.art)
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.start()
|
|
f.flush()
|
|
f.scope.dispose()
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.start()
|
|
f.art.emit('lock', false)
|
|
assert.equal(f.timers.size, 0)
|
|
})
|
|
|
|
test('destruction inside the playback rate setter cannot leave an active class', (t) => {
|
|
const f = fixture(t)
|
|
let rate = 1.5
|
|
Object.defineProperty(f.art, 'playbackRate', {
|
|
get: () => rate,
|
|
set(value) {
|
|
rate = value
|
|
if (value === 3)
|
|
f.scope.dispose()
|
|
},
|
|
})
|
|
f.start()
|
|
f.flush()
|
|
assert.equal(rate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
assert.equal(f.timers.size, 0)
|
|
})
|
|
|
|
test('failed rate assignments release press state and preserve the thrown value', (t) => {
|
|
const f = fixture(t)
|
|
const failure = new Error('rate refused')
|
|
Object.defineProperty(f.art, 'playbackRate', {
|
|
get: () => 1.5,
|
|
set(value) {
|
|
if (value === 3)
|
|
throw failure
|
|
},
|
|
})
|
|
f.start()
|
|
assert.throws(f.flush, error => error === failure)
|
|
assert.equal(f.plugin.state, false)
|
|
assert.equal(f.timers.size, 0)
|
|
f.scope.dispose()
|
|
})
|
|
|
|
test('source replacement cancels pending work and callback-time flags are rechecked', (t) => {
|
|
const f = fixture(t)
|
|
f.start()
|
|
const stale = [...f.timers.values()][0].callback
|
|
beginSource(f.art)
|
|
assert.equal(f.timers.size, 0)
|
|
stale()
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
f.start()
|
|
f.art.playing = false
|
|
f.flush()
|
|
assert.equal(f.plugin.state, false)
|
|
f.art.playing = true
|
|
f.start()
|
|
f.art.isLock = true
|
|
f.flush()
|
|
assert.equal(f.art.playbackRate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.scope.dispose()
|
|
})
|
|
|
|
test('a new press started during rate restoration supersedes the outer restart', (t) => {
|
|
const f = fixture(t)
|
|
f.start()
|
|
f.flush()
|
|
let rate = 3
|
|
let reentered = false
|
|
Object.defineProperty(f.art, 'playbackRate', {
|
|
get: () => rate,
|
|
set(value) {
|
|
rate = value
|
|
if (value === 1.5 && !reentered) {
|
|
reentered = true
|
|
f.start()
|
|
f.flush()
|
|
}
|
|
},
|
|
})
|
|
f.start()
|
|
assert.equal(f.timers.size, 0)
|
|
assert.equal(f.plugin.state, true)
|
|
assert.equal(rate, 3)
|
|
f.art.emit('document:touchend', new Event('touchend'))
|
|
assert.equal(rate, 1.5)
|
|
assert.equal(f.plugin.state, false)
|
|
f.scope.dispose()
|
|
})
|