fix(thumbnail): [PKG-TOOL-THUMB-03] own input resources and separate sheet export

This commit is contained in:
Harvey Zhao committed 2026-09-13 03:51:26 +08:00
1 parent 1cb7779192
commit 426c0c5d42
22 files changed
+13724 -256

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
import { fileURLToPath } from 'node:url'
import { hash } from '../../refactor/scripts/releases.mjs'
import { thumbnailCandidate } from '../helpers/thumbnail.js'
import { expect, test } from './fixtures.js'
const implementation = await thumbnailCandidate()
const sample = fileURLToPath(new URL('./media/thumbnail-pattern.mp4', import.meta.url))
test('Thumbnail input ownership replaces real DOM listeners and generated inputs', async ({ page }, testInfo) => {
await page.goto('/test/player.html?core=published')
await page.setContent('<!doctype html><div id="first" style="position:sticky"></div><div id="second" style="position:absolute"></div>')
await page.addScriptTag({ content: implementation.code })
await page.evaluate(() => {
window.inputEvents = []
window.inputTool = new window.ArtplayerToolThumbnail({ fileInput: document.querySelector('#first') })
window.firstInput = window.inputTool.option.fileInput
window.inputTool.on('file', file => window.inputEvents.push(file.name))
window.inputTool.setup({ fileInput: document.querySelector('#first') })
})
await expect(page.locator('#first input')).toHaveCount(1)
await page.locator('#first input').setInputFiles(sample)
const switched = await page.evaluate(() => {
const tool = window.inputTool
const firstInput = window.firstInput
const value = firstInput.value
tool.setup({ fileInput: document.querySelector('#second') })
firstInput.dispatchEvent(new Event('change'))
firstInput.dispatchEvent(new Event('drop'))
return { value, events: window.inputEvents.slice(), firstPosition: document.querySelector('#first').style.position, disconnected: !firstInput.isConnected }
})
expect(switched).toEqual({ value: '', events: ['thumbnail-pattern.mp4'], firstPosition: 'sticky', disconnected: true })
await expect(page.locator('#first input')).toHaveCount(0)
await page.locator('#second input').setInputFiles(sample)
const result = await page.evaluate(() => {
const tool = window.inputTool
const input = tool.option.fileInput
const error = new Error('expected destroyed callback')
let notifications = 0
tool.on('destroy', () => {
notifications++
throw error
})
let sameError = false
try {
tool.destroy()
}
catch (thrown) { sameError = thrown === error }
tool.destroy()
input.dispatchEvent(new Event('change'))
input.dispatchEvent(new Event('drop'))
tool.setup({ fileInput: document.querySelector('#first') })
return { events: window.inputEvents, sameError, notifications, inputConnected: input.isConnected, videoConnected: tool.video.isConnected, secondPosition: document.querySelector('#second').style.position, inputs: document.querySelectorAll('input').length }
})
expect(result).toEqual({ events: ['thumbnail-pattern.mp4', 'thumbnail-pattern.mp4'], sameError: true, notifications: 1, inputConnected: false, videoConnected: false, secondPosition: 'absolute', inputs: 0 })
await testInfo.attach('thumbnail-input-ownership', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), scope: 'Native file input/DOM/listeners only; does not claim media decoding.', switched, result }) })
})
+6 -5
View File
@@ -3,7 +3,7 @@ import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { hash } from '../../refactor/scripts/releases.mjs'
import { thumbnailHistorical } from '../helpers/thumbnail.js'
import { thumbnailCandidate, thumbnailHistorical } from '../helpers/thumbnail.js'
import { expect, test } from './fixtures.js'
const sample = fileURLToPath(new URL('./media/thumbnail-pattern.mp4', import.meta.url))
@@ -27,7 +27,8 @@ async function capabilityControl(page, testInfo) {
return true
}
for (const implementation of thumbnailHistorical()) {
const implementations = [...thumbnailHistorical(), { ...await thumbnailCandidate(), inputOwnership: true }]
for (const implementation of implementations) {
test.describe(`Thumbnail tool ${implementation.name}`, () => {
test.beforeEach(async ({ page }, testInfo) => {
await page.goto('/test/player.html?core=published')
@@ -127,7 +128,7 @@ for (const implementation of thumbnailHistorical()) {
await testInfo.attach('thumbnail-extraction', { contentType: 'application/json', body: JSON.stringify(result) })
})
test('DOM drop dispatch exposes missing listener while callable ondrop works', async ({ page }, testInfo) => {
test(`DOM drop dispatch ${implementation.inputOwnership ? 'uses the registered handler' : 'exposes missing listener'} while callable ondrop works`, async ({ page }, testInfo) => {
if (await capabilityControl(page, testInfo))
return
const result = await page.evaluate(async () => {
@@ -142,7 +143,7 @@ for (const implementation of thumbnailHistorical()) {
tool.ondrop(event)
return { fromListener, afterDirect: observed.filter(item => item.event === 'file').length, prevented: event.defaultPrevented }
})
expect(result).toEqual({ fromListener: { files: 0, prevented: false }, afterDirect: 1, prevented: true })
expect(result).toEqual({ fromListener: { files: implementation.inputOwnership ? 1 : 0, prevented: !!implementation.inputOwnership }, afterDirect: implementation.inputOwnership ? 2 : 1, prevented: true })
await expect.poll(() => page.evaluate(() => tool.video.readyState)).toBeGreaterThanOrEqual(2)
await page.evaluate(() => tool.destroy())
})
@@ -169,7 +170,7 @@ for (const implementation of thumbnailHistorical()) {
expect(result.created).toHaveLength(2)
expect(result.revoked).toEqual([result.created[1]])
expect(result.revoked).not.toContain(failed.first)
expect(result.repeated).toBe('NotFoundError')
expect(result.repeated).toBe(implementation.inputOwnership ? undefined : 'NotFoundError')
expect(result.value === '').toBe(!implementation.legacy)
await testInfo.attach('thumbnail-media-failure', { contentType: 'application/json', body: JSON.stringify({ failed, result }) })
})