mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-24 23:51:20 -08:00
refactor(core): CORE-18 type builtins and own prompt lifetimes
This commit is contained in:
1 parent
a842058136
commit
98a1800032
55 files changed
+26511
-650
No files matched your search
@@ -0,0 +1,229 @@
|
||||
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()
|
||||
})
|
||||
Reference in new issue
Block a user