Files
ArtPlayer/test/thumbnail-runtime.test.js

154 lines
6.5 KiB
JavaScript

import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Runtime compatibility tests.
import test from 'node:test'
import vm from 'node:vm'
import { thumbnailCandidate, thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js'
const candidate = await thumbnailCandidate()
function create(implementation, option = {}) {
const env = thumbnailEnvironment(implementation)
return { ...env, tool: new env.Factory({ fileInput: new env.Element('input'), ...option }) }
}
const referenceModule = { exports: {} }
vm.runInNewContext(fs.readFileSync(new URL('../refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt', import.meta.url), 'utf8'), { module: referenceModule })
const Upstream = referenceModule.exports
const upstream = () => new Upstream()
const emitters = [upstream, ...thumbnailHistorical().map(implementation => () => create(implementation).tool), () => create(candidate).tool]
test('Thumbnail typed facade preserves descriptors, lazy fields, default class and unknown options', () => {
const { tool, Factory, exported } = create(candidate)
const baseline = create(thumbnailHistorical().find(item => item.name === 'workspace.js'))
const describe = object => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(object)).map(([name, value]) => [name, {
enumerable: value.enumerable,
configurable: value.configurable,
writable: value.writable,
getter: typeof value.get,
setter: typeof value.set,
type: typeof value.value,
arity: typeof value.value === 'function' ? value.value.length : undefined,
}]))
assert.deepEqual(describe(Factory.prototype), describe(baseline.Factory.prototype))
assert.deepEqual(describe(Factory), describe(baseline.Factory))
assert.deepEqual(describe(tool), describe(baseline.tool))
assert.deepEqual(Object.keys(tool), ['processing', 'option', 'video', 'duration', 'inputChange', 'ondrop'])
assert.equal(exported.default, undefined)
assert.equal(typeof thumbnailEnvironment(candidate, true).exported, 'function')
const metadata = { id: 123 }
assert.equal(tool.setup({ custom: metadata, width: 21.5 }), tool)
assert.equal(tool.option.custom, metadata)
assert.equal(tool.option.width, 21.5)
assert.notEqual(Factory.DEFAULTS, Factory.DEFAULTS)
const published = thumbnailEnvironment(thumbnailHistorical().find(item => item.legacy))
assert.deepEqual(Object.keys(Factory.DEFAULTS), Object.keys(published.Factory.DEFAULTS))
assert(Number.isNaN(Factory.DEFAULTS.end))
tool.destroy()
})
test('Thumbnail emitter keeps upstream snapshot order, duplicate removal and original once callback identity', () => {
for (const create of emitters) {
const emitter = create()
const seen = []
const callback = value => seen.push(value)
assert.equal(emitter.on('value', () => emitter.off('value', callback)), emitter)
emitter.on('value', callback).once('value', callback).on('value', callback)
emitter.emit('value', 7).emit('value', 8)
assert.deepEqual(seen, [7, 7, 7])
assert.equal(emitter.off('value'), emitter)
assert.equal(emitter.e.value, undefined)
emitter.once('removed', callback).off('removed', callback).emit('removed', 9)
assert.deepEqual(seen, [7, 7, 7])
}
})
test('Thumbnail emitter preserves ctx, variadic arguments, symbol events and ignored callback returns', () => {
for (const create of emitters) {
const emitter = create()
const ctx = { calls: 0 }
const name = Symbol('event')
function callback(...args) {
assert.equal(this, ctx)
assert.deepEqual(args, [1, 'two', null])
this.calls++
return 123
}
assert.equal(emitter.on(name, callback, ctx), emitter)
assert.equal(emitter.once(name, callback, ctx), emitter)
assert.equal(emitter.emit(name, 1, 'two', null), emitter)
assert.equal(ctx.calls, 2)
emitter.off(name, callback).emit(name)
assert.equal(ctx.calls, 2)
}
})
test('Thumbnail emitter synchronous listener failures retain identity and stop the current snapshot', () => {
for (const create of emitters) {
const emitter = create()
const failure = { reason: 'listener failure' }
let later = 0
emitter.once('event', () => {
throw failure
})
emitter.on('event', () => later++)
assert.throws(() => emitter.emit('event'), error => error === failure)
assert.equal(later, 0)
emitter.emit('event')
assert.equal(later, 1)
}
})
test('Thumbnail emitter additions during dispatch wait until the next snapshot', () => {
for (const create of emitters) {
const emitter = create()
const seen = []
emitter.once('event', () => {
seen.push('first')
emitter.on('event', () => seen.push('new'))
})
emitter.on('event', () => seen.push('second'))
emitter.emit('event')
assert.deepEqual(seen, ['first', 'second'])
emitter.emit('event')
assert.deepEqual(seen, ['first', 'second', 'second', 'new'])
}
})
test('Thumbnail typed facade retains synchronous validation, option mutation and grid geometry', () => {
const { tool } = create(candidate)
tool.setup({ number: 10, width: 20, height: 30, column: 3, begin: 5 })
tool.duration = 100
assert.deepEqual(JSON.parse(JSON.stringify(tool.creatScreenshotDate())), Array.from({ length: 10 }, (_, i) => ({ time: 10 + i * 10, x: i % 3 * 20, y: Math.floor(i / 3) * 30 })))
const canvas = tool.creatCanvas()
assert.equal(canvas.width, 60)
assert.equal(canvas.height, 150)
const errors = []
tool.on('error', value => errors.push(value))
assert.throws(() => tool.setup({ width: '80' }), /not a number/)
assert.equal(tool.option.width, 20)
assert.throws(() => tool.download(), /does not seem to be ready/)
assert.equal(errors.length, 2)
assert.equal(tool.errorHandle(true, 'unused'), undefined)
tool.destroy()
})
test('Thumbnail error event preserves arbitrary thrown message values and Promise rejection identity', async () => {
for (const failure of [{ message: 7 }, { message: { detail: 'failure' } }, 3, null]) {
const { tool } = create(candidate, { compatibility: 'workspace-4.4' })
tool.setup({ number: 10 })
tool.file = { name: 'sample.mp4' }
tool.video.duration = 100
const errors = []
tool.on('error', value => errors.push(value))
tool.on('update', () => {
throw failure
})
const pending = tool.start()
await Promise.resolve()
tool.video.oncanplay()
await assert.rejects(pending, error => error === failure)
assert.deepEqual(errors, [failure?.message])
assert.equal(tool.processing, false)
tool.destroy()
}
})