mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
219 lines
9.2 KiB
JavaScript
219 lines
9.2 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Candidate fixes also run against the frozen baseline.
|
|
import test from 'node:test'
|
|
import { thumbnailCandidate, thumbnailEnvironment } from './helpers/thumbnail.js'
|
|
|
|
const implementation = await thumbnailCandidate()
|
|
function setup(wrapper = false) {
|
|
const env = thumbnailEnvironment(implementation)
|
|
const target = new env.Element(wrapper ? 'div' : 'input')
|
|
const instance = new env.Factory({ fileInput: target, number: 10, width: 20, height: 30, column: 3, compatibility: 'workspace-4.4' })
|
|
return { ...env, target, instance }
|
|
}
|
|
|
|
test('Thumbnail input ownership preserves class methods, constructor fields and defaults', () => {
|
|
const { instance, Factory, exported } = setup()
|
|
assert.equal(typeof exported, 'function')
|
|
assert.equal(exported.default, undefined)
|
|
assert.deepEqual(Object.keys(instance), ['processing', 'option', 'video', 'duration', 'inputChange', 'ondrop'])
|
|
assert.deepEqual(Object.getOwnPropertyNames(Factory.prototype), ['constructor', 'ondrop', 'setup', 'inputChange', 'loadVideo', 'start', 'creatScreenshotDate', 'creatCanvas', 'download', 'errorHandle', 'destroy'])
|
|
assert.deepEqual(Object.keys(Factory.DEFAULTS), ['delay', 'number', 'width', 'height', 'column', 'begin', 'end'])
|
|
assert.equal(instance.setup({ custom: 7 }), instance)
|
|
assert.equal(instance.option.custom, 7)
|
|
instance.destroy()
|
|
})
|
|
|
|
test('Thumbnail actual registered drop callback loads the first file and prevents default', () => {
|
|
const { instance, target } = setup()
|
|
const file = { name: 'drop.mp4', type: 'video/mp4' }
|
|
let prevented = 0
|
|
for (const callback of target.handlers.get('drop') || [])
|
|
callback({ preventDefault() { prevented++ }, dataTransfer: { files: [file] } })
|
|
assert.equal(instance.file, file)
|
|
assert.equal(prevented, 1)
|
|
assert.equal(target.handlers.get('drop').has(instance.ondrop), true)
|
|
instance.destroy()
|
|
})
|
|
|
|
test('Thumbnail replacing input moves all listeners and reads the new file', () => {
|
|
const { instance, target, Element } = setup()
|
|
const next = new Element('input')
|
|
assert.equal(instance.setup({ fileInput: next }), instance)
|
|
for (const name of ['change', 'dragover', 'drop']) {
|
|
assert.equal(target.handlers.get(name)?.size || 0, 0)
|
|
assert.equal(next.handlers.get(name)?.size, 1)
|
|
}
|
|
next.files = [{ name: 'new.mp4', type: 'video/mp4' }]
|
|
next.value = 'new.mp4'
|
|
for (const callback of next.handlers.get('change'))
|
|
callback({ target: next })
|
|
assert.equal(instance.file, next.files[0])
|
|
assert.equal(next.value, '')
|
|
instance.destroy()
|
|
assert.equal(next.handlers.get('change').size, 0)
|
|
})
|
|
|
|
test('Thumbnail repeated wrapper setup reuses the owned input and restores replaced wrapper style', () => {
|
|
const env = thumbnailEnvironment(implementation)
|
|
const first = new env.Element('div')
|
|
first.style.position = 'sticky'
|
|
const instance = new env.Factory({ fileInput: first })
|
|
const input = instance.option.fileInput
|
|
instance.setup({ fileInput: first })
|
|
assert.equal(first.children.length, 1)
|
|
assert.equal(instance.option.fileInput, input)
|
|
const second = new env.Element('div')
|
|
second.style.position = 'absolute'
|
|
instance.setup({ fileInput: second })
|
|
assert.equal(first.children.length, 0)
|
|
assert.equal(first.style.position, 'sticky')
|
|
assert.equal(input.handlers.get('change').size, 0)
|
|
instance.destroy()
|
|
assert.equal(second.children.length, 0)
|
|
assert.equal(second.style.position, 'absolute')
|
|
})
|
|
|
|
test('Thumbnail does not overwrite a wrapper style changed by its owner', () => {
|
|
const { instance, target } = setup(true)
|
|
target.style.position = 'fixed'
|
|
instance.destroy()
|
|
assert.equal(target.children.length, 0)
|
|
assert.equal(target.style.position, 'fixed')
|
|
})
|
|
|
|
test('Thumbnail setup validation is atomic and cannot append an orphan input', () => {
|
|
const { instance, Element } = setup()
|
|
const previous = instance.option
|
|
const wrapper = new Element('div')
|
|
assert.throws(() => instance.setup({ fileInput: wrapper, width: 'invalid' }), /width/)
|
|
assert.equal(instance.option, previous)
|
|
assert.equal(wrapper.children.length, 0)
|
|
assert.equal(wrapper.style.position, undefined)
|
|
instance.destroy()
|
|
})
|
|
|
|
test('Thumbnail failed replacement listener installation removes partial listeners and keeps old ownership', () => {
|
|
const { instance, target, Element } = setup()
|
|
const next = new Element('input')
|
|
const failure = new Error('listener failed')
|
|
const add = next.addEventListener
|
|
next.addEventListener = function (name, callback) {
|
|
add.call(this, name, callback)
|
|
if (name === 'dragover')
|
|
throw failure
|
|
}
|
|
assert.throws(() => instance.setup({ fileInput: next }), error => error === failure)
|
|
assert.equal(instance.option.fileInput, target)
|
|
assert.equal(target.handlers.get('change').size, 1)
|
|
for (const callbacks of next.handlers.values())
|
|
assert.equal(callbacks.size, 0)
|
|
instance.destroy()
|
|
})
|
|
|
|
test('Thumbnail failed video attachment rolls back generated input and attached video', () => {
|
|
const env = thumbnailEnvironment(implementation)
|
|
const wrapper = new env.Element('div')
|
|
wrapper.style.position = 'fixed'
|
|
const failure = new Error('append failed')
|
|
const append = env.body.appendChild
|
|
env.body.appendChild = function (child) {
|
|
append.call(this, child)
|
|
throw failure
|
|
}
|
|
assert.throws(() => new env.Factory({ fileInput: wrapper }), error => error === failure)
|
|
assert.equal(env.body.children.length, 0)
|
|
assert.equal(wrapper.children.length, 0)
|
|
assert.equal(wrapper.style.position, 'fixed')
|
|
})
|
|
|
|
test('Thumbnail failed constructor listener attachment removes every partial resource', () => {
|
|
const env = thumbnailEnvironment(implementation)
|
|
const target = new env.Element('input')
|
|
const failure = new Error('drop registration failed')
|
|
const add = target.addEventListener
|
|
target.addEventListener = function (name, callback) {
|
|
add.call(this, name, callback)
|
|
if (name === 'drop')
|
|
throw failure
|
|
}
|
|
assert.throws(() => new env.Factory({ fileInput: target }), error => error === failure)
|
|
assert.equal(env.body.children.length, 0)
|
|
for (const callbacks of target.handlers.values())
|
|
assert.equal(callbacks.size, 0)
|
|
})
|
|
|
|
test('Thumbnail externally detached video and repeated destroy are safe', () => {
|
|
const { instance, body, target } = setup()
|
|
let count = 0
|
|
instance.on('destroy', () => count++)
|
|
body.removeChild(instance.video)
|
|
assert.equal(instance.destroy(), undefined)
|
|
assert.equal(instance.destroy(), undefined)
|
|
assert.equal(count, 1)
|
|
for (const callbacks of target.handlers.values())
|
|
assert.equal(callbacks.size, 0)
|
|
})
|
|
|
|
test('Thumbnail destroy uses private input ownership despite public option replacement', () => {
|
|
const { instance, target, Element } = setup(true)
|
|
const ownedInput = instance.option.fileInput
|
|
instance.option = { fileInput: new Element('input') }
|
|
instance.destroy()
|
|
assert.equal(target.children.length, 0)
|
|
for (const callbacks of ownedInput.handlers.values())
|
|
assert.equal(callbacks.size, 0)
|
|
})
|
|
|
|
test('Thumbnail cleanup continues after listener removal failure and cannot reload after destroy', () => {
|
|
const { instance, target, body, operations, Element } = setup()
|
|
instance.loadVideo({ name: 'first.mp4', type: 'video/mp4' })
|
|
const failure = new Error('remove failed')
|
|
const remove = target.removeEventListener
|
|
target.removeEventListener = function (name, callback) {
|
|
if (name === 'change')
|
|
throw failure
|
|
remove.call(this, name, callback)
|
|
}
|
|
let count = 0
|
|
instance.on('destroy', () => count++)
|
|
assert.throws(() => instance.destroy(), error => error === failure)
|
|
assert.equal(body.children.length, 0)
|
|
assert.equal(count, 1)
|
|
assert.equal(target.handlers.get('dragover').size, 0)
|
|
assert.equal(operations.filter(item => item.name === 'revokeURL').length, 1)
|
|
const wrapper = new Element('div')
|
|
assert.equal(instance.setup({ fileInput: wrapper }), instance)
|
|
instance.loadVideo({ name: 'late.mp4', type: 'video/mp4' })
|
|
instance.inputChange({ target })
|
|
instance.ondrop({ preventDefault() {
|
|
throw new Error('should not run')
|
|
} })
|
|
assert.equal(wrapper.children.length, 0)
|
|
assert.equal(operations.filter(item => item.name === 'createURL').length, 1)
|
|
assert.equal(instance.destroy(), undefined)
|
|
})
|
|
|
|
test('Thumbnail download removes its temporary anchor even when click fails', () => {
|
|
const { instance, Element, body } = setup()
|
|
instance.file = { name: 'video.part.mp4' }
|
|
instance.thumbnailUrl = 'blob:sheet'
|
|
const failure = new Error('click failed')
|
|
Element.prototype.click = () => {
|
|
throw failure
|
|
}
|
|
assert.throws(() => instance.download(), error => error === failure)
|
|
assert.deepEqual(body.children.map(child => child.tagName), ['VIDEO'])
|
|
instance.destroy()
|
|
})
|
|
|
|
test('Thumbnail extracted sheet helpers preserve midpoint coordinates and footer', () => {
|
|
const { instance, operations } = setup()
|
|
instance.duration = 100
|
|
assert.deepEqual(JSON.parse(JSON.stringify(instance.creatScreenshotDate())), Array.from({ length: 10 }, (_, i) => ({ time: 5 + i * 10, x: i % 3 * 20, y: Math.floor(i / 3) * 30 })))
|
|
const canvas = instance.creatCanvas()
|
|
assert.equal(canvas.width, 60)
|
|
assert.equal(canvas.height, 150)
|
|
assert.deepEqual(operations.find(item => item.name === 'fillText').args, ['From: https://artplayer.org/, Number: 10, Width: 20, Height: 30, Column: 3', 10, 139])
|
|
instance.destroy()
|
|
})
|