Files
ArtPlayer/test/auto-thumbnail.test.js
T

141 lines
6.2 KiB
JavaScript

import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Frozen historical regressions use the repository Node runner.
import test from 'node:test'
import { autoThumbnailEnvironment, autoThumbnailHistorical } from './helpers/auto-thumbnail.js'
for (const implementation of await autoThumbnailHistorical()) {
test(`Auto-thumbnail ${implementation.name} historical: destroy leaves pending encode and final URL alive`, async () => {
const env = autoThumbnailEnvironment(implementation)
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
env.metadata()
const video = env.videos[0]
env.art.emit('destroy')
env.finish()
assert.equal(env.updates.length, 1)
assert.equal(env.urls.size, 1)
assert.equal(env.listeners.get('video:loadedmetadata').size, 1)
assert.equal(typeof video.onloadedmetadata, 'function')
assert.equal(typeof video.onseeked, 'function')
assert.equal(video.src, 'original.mp4')
assert.equal(env.operations.filter(item => item.name === 'pause' || item.name === 'load').length, 0)
env.art.emit('destroy')
assert.equal(env.urls.size, 1)
})
test(`Auto-thumbnail ${implementation.name} historical: delayed old metadata overwrites a newer source sheet`, async () => {
const env = autoThumbnailEnvironment(implementation)
const options = { number: 2, width: 80 }
await env.factory(options)(env.art)
env.art.emit('video:loadedmetadata')
const oldVideo = env.videos[0]
env.art.option.url = 'replacement.mp4'
options.width = 160
env.art.emit('restart')
env.art.emit('video:loadedmetadata')
env.metadata(env.videos[1])
env.finish(0, { source: 'new' })
assert.equal(env.updates.at(-1).width, 160)
env.metadata(oldVideo)
env.finish(1, { source: 'old' })
assert.equal(env.updates.at(-1).width, 80)
assert.equal(env.urls.get(env.updates.at(-1).url).source, 'old')
assert.equal(env.urls.size, 2, 'Separate jobs never revoke each other or release the latest stale sheet')
})
test(`Auto-thumbnail ${implementation.name} historical: repeated metadata opens independent decoders without closing previous jobs`, async () => {
const env = autoThumbnailEnvironment(implementation)
await env.factory({ number: 1 })(env.art)
for (let index = 0; index < 3; index++) {
env.art.emit('video:loadedmetadata')
env.metadata()
env.finish(index)
}
assert.equal(env.videos.length, 3)
assert.equal(env.urls.size, 3)
assert.equal(env.operations.some(item => item.name === 'pause' || item.name === 'load'), false)
assert.equal(env.videos.every(video => typeof video.onseeked === 'function'), true)
})
test(`Auto-thumbnail ${implementation.name} historical: reverse Blob delivery replaces a complete sheet with its initial blank snapshot`, async () => {
const env = autoThumbnailEnvironment(implementation)
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
env.metadata()
env.seeked()
env.seeked()
assert.equal(env.blobs.length, 3, 'All native encodes can be pending together')
env.finish(2, { frames: 2 })
const completed = env.updates.at(-1).url
env.finish(0, { frames: 0 })
assert.equal(env.urls.has(completed), false)
assert.equal(env.urls.get(env.updates.at(-1).url).frames, 0)
env.finish(1, { frames: 1 })
assert.equal(env.urls.get(env.updates.at(-1).url).frames, 1)
})
test(`Auto-thumbnail ${implementation.name} historical: null Blob revokes the previous usable URL before throwing`, async () => {
const env = autoThumbnailEnvironment(implementation)
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
env.metadata()
env.finish()
const sheet = env.art.thumbnails
env.seeked()
assert.throws(() => env.finish(1, null), /Blob required/)
assert.equal(env.urls.has(sheet.url), false)
assert.equal(env.art.thumbnails, sheet, 'Public state still references the revoked URL')
assert.equal(typeof env.videos[0].onseeked, 'function')
})
test(`Auto-thumbnail ${implementation.name} historical: draw and encode exceptions escape and retain decoder callbacks`, async () => {
for (const phase of ['draw', 'encode']) {
const env = autoThumbnailEnvironment(implementation)
const failure = new Error(`${phase} failed`)
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
if (phase === 'draw') {
env.metadata()
env.controls.drawError = failure
assert.throws(() => env.seeked(), error => error === failure)
}
else {
env.controls.encodeError = failure
assert.throws(() => env.metadata(), error => error === failure)
}
assert.equal(typeof env.videos[0].onloadedmetadata, 'function')
assert.equal(env.videos[0].src, 'original.mp4')
assert.equal(env.operations.some(item => item.name === 'pause' || item.name === 'load'), false)
}
})
test(`Auto-thumbnail ${implementation.name} historical: missing context and native media errors have no failure owner`, async () => {
const env = autoThumbnailEnvironment(implementation)
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
env.controls.nullContext = true
env.metadata()
assert.throws(() => env.seeked(), /drawImage/)
assert.equal(env.videos[0].onerror, null)
assert.equal(env.operations.some(item => item.name === 'pause' || item.name === 'load'), false)
})
test(`Auto-thumbnail ${implementation.name} historical: thumbnail setter failure leaks the new URL and does not cancel later work`, async () => {
const env = autoThumbnailEnvironment(implementation)
const failure = new Error('thumbnail setter failed')
await env.factory({ number: 2 })(env.art)
env.art.emit('video:loadedmetadata')
env.metadata()
env.controls.updateError = failure
assert.throws(() => env.finish(), error => error === failure)
assert.equal(env.urls.size, 1)
assert.equal(env.updates.length, 0)
env.controls.updateError = null
env.seeked()
env.finish(1)
assert.equal(env.updates.length, 1)
env.art.emit('destroy')
assert.equal(env.urls.size, 1)
})
}