mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
189 lines
7.0 KiB
JavaScript
189 lines
7.0 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Approved runtime policy compatibility.
|
|
import test from 'node:test'
|
|
import { thumbnailCandidate, thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js'
|
|
|
|
const candidate = await thumbnailCandidate()
|
|
const flush = () => new Promise(resolve => setImmediate(resolve))
|
|
function create(option = {}) {
|
|
const env = thumbnailEnvironment(candidate)
|
|
env.input = new env.Element('input')
|
|
env.tool = new env.Factory({ fileInput: env.input, number: 10, width: 40, height: 25, ...option })
|
|
return env
|
|
}
|
|
function tick(env, delay) {
|
|
const selected = [...env.timers].find(([, timer]) => Object.is(timer.delay, delay))
|
|
assert(selected, `Expected owned timer for ${delay} ms`)
|
|
env.timers.delete(selected[0])
|
|
selected[1].callback()
|
|
}
|
|
function ready(env) {
|
|
env.tool.video.duration = 100
|
|
env.tool.video.readyState = 2
|
|
env.tool.video.videoWidth = 160
|
|
env.tool.video.videoHeight = 90
|
|
env.tool.loadVideo({ name: 'sample.mp4', type: 'video/mp4' })
|
|
for (const fn of env.tool.video.handlers.get('loadedmetadata'))
|
|
fn()
|
|
}
|
|
|
|
test('Thumbnail root defaults match the recovered published profile, including key order', () => {
|
|
const actual = create()
|
|
const old = thumbnailEnvironment(thumbnailHistorical().find(item => item.legacy))
|
|
assert.deepEqual(Object.keys(actual.Factory.DEFAULTS), Object.keys(old.Factory.DEFAULTS))
|
|
for (const key of Object.keys(old.Factory.DEFAULTS))
|
|
assert.equal(actual.Factory.DEFAULTS[key], old.Factory.DEFAULTS[key])
|
|
actual.tool.destroy()
|
|
})
|
|
|
|
test('Thumbnail published policy validates and clamps delay/height while workspace preserves its inputs', () => {
|
|
const published = create({ height: 2, delay: 4000 })
|
|
assert.equal(published.tool.option.height, 10)
|
|
assert.equal(published.tool.option.delay, 1000)
|
|
assert.throws(() => published.tool.setup({ delay: 'bad' }), /delay.*number/)
|
|
assert.throws(() => published.tool.setup({ height: 'bad' }), /height.*number/)
|
|
assert.throws(() => published.tool.setup({ compatibility: 'unknown' }), /compatibility/)
|
|
const workspace = create({ compatibility: 'workspace-4.4', height: 2, delay: 4000 })
|
|
assert.equal(workspace.tool.option.height, 2)
|
|
assert.equal(workspace.tool.option.delay, 4000)
|
|
published.tool.destroy()
|
|
workspace.tool.destroy()
|
|
})
|
|
|
|
for (const compatibility of [undefined, 'published-3.5', 'workspace-4.4']) {
|
|
test(`Thumbnail ${compatibility || 'default'} preserves file/video timing and input reset policy`, () => {
|
|
const env = create({ compatibility, delay: 25 })
|
|
const seen = []
|
|
env.input.files = [{ name: 'sample.mp4', type: 'video/mp4' }]
|
|
env.input.value = 'selected'
|
|
env.tool.on('file', () => seen.push(['file', env.tool.video.src]))
|
|
env.tool.on('video', video => seen.push(['video', video.src]))
|
|
env.tool.inputChange({ target: env.input })
|
|
const workspace = compatibility === 'workspace-4.4'
|
|
assert.deepEqual(seen, workspace ? [['file', undefined], ['video', env.tool.videoUrl]] : [['file', undefined]])
|
|
assert.equal(env.input.value, workspace ? '' : 'selected')
|
|
if (!workspace) {
|
|
tick(env, 25)
|
|
assert.deepEqual(seen[1], ['video', env.tool.videoUrl])
|
|
}
|
|
env.tool.destroy()
|
|
assert.equal(env.timers.size, 0)
|
|
})
|
|
}
|
|
|
|
test('Thumbnail default extraction keeps configured height and requires delay plus frame readiness', async () => {
|
|
const env = create({ delay: 25 })
|
|
ready(env)
|
|
tick(env, 25)
|
|
env.tool.video.seeking = true
|
|
const work = env.tool.start()
|
|
await flush()
|
|
assert.equal(env.tool.option.height, 25)
|
|
tick(env, 25)
|
|
assert.equal(env.operations.filter(op => op.name === 'drawImage').length, 0)
|
|
env.tool.video.seeking = false
|
|
env.tool.video.oncanplay()
|
|
await flush()
|
|
assert.equal(env.operations.filter(op => op.name === 'drawImage').length, 1)
|
|
env.tool.destroy()
|
|
await assert.rejects(work, { name: 'AbortError' })
|
|
assert.equal(env.timers.size, 0)
|
|
})
|
|
|
|
test('Thumbnail delayed video notifications are cancelled by replacement, failure and destroy', () => {
|
|
for (const action of ['replace', 'error', 'destroy']) {
|
|
const env = create({ delay: 25 })
|
|
const seen = []
|
|
env.tool.on('video', () => seen.push(env.tool.file.name)).on('error', () => {})
|
|
env.tool.loadVideo({ name: 'old.mp4', type: 'video/mp4' })
|
|
const stale = [...env.timers.values()].find(timer => timer.delay === 25)
|
|
assert(stale)
|
|
if (action === 'replace') {
|
|
env.tool.loadVideo({ name: 'new.mp4', type: 'video/mp4' })
|
|
}
|
|
else if (action === 'error') {
|
|
for (const fn of env.tool.video.handlers.get('error'))
|
|
fn()
|
|
}
|
|
else {
|
|
env.tool.destroy()
|
|
}
|
|
stale.callback()
|
|
assert.deepEqual(seen, [])
|
|
if (action === 'replace') {
|
|
tick(env, 25)
|
|
assert.deepEqual(seen, ['new.mp4'])
|
|
}
|
|
env.tool.destroy()
|
|
assert.equal(env.timers.size, 0)
|
|
}
|
|
})
|
|
|
|
test('Thumbnail numeric NaN delay keeps the historical asynchronous timer boundary', () => {
|
|
const env = create({ delay: Number.NaN })
|
|
let events = 0
|
|
env.tool.on('video', () => events++)
|
|
env.tool.loadVideo({ name: 'sample.mp4', type: 'video/mp4' })
|
|
assert.equal(events, 0)
|
|
tick(env, Number.NaN)
|
|
assert.equal(events, 1)
|
|
env.tool.destroy()
|
|
})
|
|
|
|
for (const finish of ['complete', 'destroy', 'replace', 'error']) {
|
|
test(`Thumbnail default final delay owns completion and ${finish} settlement`, async () => {
|
|
const env = create({ delay: 25 })
|
|
ready(env)
|
|
tick(env, 25)
|
|
let done = 0
|
|
const updates = []
|
|
env.tool.on('done', () => done++).on('update', (_, progress) => updates.push(progress))
|
|
const work = env.tool.start()
|
|
await flush()
|
|
for (let frame = 0; frame < 10; frame++) {
|
|
tick(env, 25)
|
|
await flush()
|
|
}
|
|
assert.deepEqual(updates, Array.from({ length: 10 }, (_, index) => (index + 1) / 10))
|
|
assert.equal(done, 0)
|
|
assert.equal(env.tool.processing, true)
|
|
const final = [...env.timers.values()].find(timer => timer.delay === 50)
|
|
assert(final)
|
|
if (finish === 'complete') {
|
|
tick(env, 50)
|
|
await work
|
|
assert.equal(done, 1)
|
|
}
|
|
else {
|
|
if (finish === 'destroy') {
|
|
env.tool.destroy()
|
|
}
|
|
else if (finish === 'replace') {
|
|
env.tool.loadVideo({ name: 'next.mp4', type: 'video/mp4' })
|
|
}
|
|
else {
|
|
env.tool.on('error', () => {})
|
|
for (const fn of [...env.tool.video.handlers.get('error')])
|
|
fn()
|
|
}
|
|
await assert.rejects(work, finish === 'error' ? /Unable to load video/ : { name: 'AbortError' })
|
|
final.callback()
|
|
assert.equal(done, 0)
|
|
}
|
|
assert.equal(env.tool.processing, false)
|
|
env.tool.destroy()
|
|
assert.equal(env.timers.size, 0)
|
|
})
|
|
}
|
|
|
|
test('Thumbnail explicit workspace extraction retains aspect height with no policy timers', async () => {
|
|
const env = create({ compatibility: 'workspace-4.4', delay: 25 })
|
|
ready(env)
|
|
const work = env.tool.start()
|
|
await work
|
|
assert.equal(env.tool.option.height, 22.5)
|
|
assert.equal(env.operations.filter(op => op.name === 'drawImage').length, 10)
|
|
assert.equal(env.timers.size, 0)
|
|
env.tool.destroy()
|
|
})
|