Files
ArtPlayer/test/canvas.test.js
T

174 lines
6.9 KiB
JavaScript

import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Node historical regression runner.
import test from 'node:test'
import { canvasEnvironment, canvasHistorical } from './helpers/canvas.js'
function deferred() {
let resolve, reject
const promise = new Promise((yes, no) => {
resolve = yes
reject = no
})
return { promise, resolve, reject }
}
for (const implementation of await canvasHistorical()) {
test(`Canvas ${implementation.name}: historical pending bitmap draws and reschedules after pause or destroy`, async () => {
for (const operation of ['video:pause', 'destroy']) {
const pending = deferred()
let closed = 0
const bitmap = { close() {
closed++
} }
const env = await canvasEnvironment(implementation, { createImageBitmap: () => pending.promise })
env.factory()(env.art)
env.art.emit('video:play')
env.art.emit(operation)
assert.equal(env.frames.size, 0)
pending.resolve(bitmap)
await env.flush()
assert.equal(env.draws.length, 1)
assert.equal(closed, 1)
assert.equal(env.frames.size, 1)
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:draw')
}
})
test(`Canvas ${implementation.name}: historical repeated play creates concurrent stale bitmap loops`, async () => {
const pending = [deferred(), deferred()]
let index = 0
const env = await canvasEnvironment(implementation, { createImageBitmap: () => pending[index++].promise })
env.factory()(env.art)
env.art.emit('video:play')
env.art.emit('video:play')
const newer = { close() {} }
const older = { close() {} }
pending[1].resolve(newer)
await env.flush()
pending[0].resolve(older)
await env.flush()
assert.deepEqual(env.draws.map(args => args[0]), [newer, older])
assert.equal(env.frames.size, 2)
env.art.destroy()
assert.equal(env.frames.size, 1, 'Only the latest stored RAF handle is cancelled')
})
test(`Canvas ${implementation.name}: historical draw failure emits its error but leaks the acquired bitmap`, async () => {
const failure = new Error('draw failed')
let closed = 0
const bitmap = { close() {
closed++
} }
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.resolve(bitmap), onDraw() {
throw failure
} })
env.factory()(env.art)
env.art.emit('video:play')
await env.flush()
assert.equal(closed, 0)
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
assert.equal(env.emitted.at(-1).args[0], failure)
assert.equal(env.frames.size, 1)
})
test(`Canvas ${implementation.name}: historical bitmap rejection emits the original error without direct-video fallback`, async () => {
const failure = new Error('bitmap rejected')
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.reject(failure) })
env.factory()(env.art)
env.art.emit('video:play')
await env.flush()
assert.equal(env.draws.length, 0)
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
assert.equal(env.emitted.at(-1).args[0], failure)
assert.equal(env.frames.size, 1)
})
test(`Canvas ${implementation.name}: callback errors occur after bitmap close and replace the draw event with an error`, async () => {
const failure = new Error('callback failed')
let closed = 0
const env = await canvasEnvironment(implementation, { createImageBitmap: () => Promise.resolve({ close() {
closed++
} }) })
env.factory(() => {
throw failure
})(env.art)
env.art.emit('resize')
await env.flush()
assert.equal(closed, 1)
assert.equal(env.emitted.filter(event => event.name === 'artplayerProxyCanvas:draw').length, 0)
assert.equal(env.emitted.at(-1).args[0], failure)
})
test(`Canvas ${implementation.name}: historical callback destruction still emits draw and queues a new frame`, async () => {
const env = await canvasEnvironment(implementation)
env.factory(() => env.art.destroy())(env.art)
env.art.emit('video:play')
await env.flush()
assert.deepEqual(env.emitted.map(event => event.name), ['video:play', 'destroy', 'artplayerProxyCanvas:draw'])
assert.equal(env.frames.size, 1)
})
test(`Canvas ${implementation.name}: historical destroy before setup timeout retains host handlers and attaches media listeners later`, async () => {
const env = await canvasEnvironment(implementation)
env.factory()(env.art)
env.art.destroy()
assert.equal(env.timers.size, 1)
assert.equal([...env.listeners.values()].reduce((count, set) => count + set.size, 0), 5)
env.flushTimers()
assert.equal(env.proxies.length, 5)
env.emitVideo({ type: 'play' })
await env.flush()
assert.equal(env.frames.size, 1)
})
test(`Canvas ${implementation.name}: historical late attachment to a destroyed host still registers and draws`, async () => {
const env = await canvasEnvironment(implementation)
env.art.destroy()
const result = env.factory()(env.art)
assert.equal(result, env.canvas)
env.flushTimers()
env.emitVideo({ type: 'play' })
await env.flush()
assert.equal(env.proxies.length, 5)
assert.equal(env.draws.length, 1)
assert.equal(env.frames.size, 1)
})
test(`Canvas ${implementation.name}: historical setup subscription failure leaves deferred work and the partial host registration`, async () => {
const env = await canvasEnvironment(implementation)
const on = env.art.on
const failure = new Error('subscription failed')
env.art.on = (...args) => {
on(...args)
throw failure
}
assert.throws(() => env.factory()(env.art), error => error === failure)
assert.equal(env.timers.size, 1)
assert.equal([...env.listeners.values()].reduce((count, set) => count + set.size, 0), 1)
env.flushTimers()
assert.equal(env.proxies.length, 5)
})
test(`Canvas ${implementation.name}: historical zero media dimensions reach unguarded resize and drawing`, async () => {
const env = await canvasEnvironment(implementation)
env.video.videoWidth = 0
env.video.videoHeight = 0
env.factory()(env.art)
env.art.emit('resize')
await env.flush()
assert(Number.isNaN(env.canvas.height), 'This controlled element does not apply native unsigned dimension coercion')
assert.equal(env.canvas.style.padding, 'NaNpx 0px')
assert.equal(env.draws.length, 1)
})
test(`Canvas ${implementation.name}: historical missing 2D context repeats a TypeError path with no capability guard`, async () => {
const env = await canvasEnvironment(implementation, { noContext: true })
env.factory()(env.art)
env.art.emit('video:play')
await env.flush()
assert.equal(env.emitted.at(-1).name, 'artplayerProxyCanvas:error')
assert.equal(env.emitted.at(-1).args[0].name, 'TypeError')
assert.equal(env.frames.size, 1)
})
}