mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-14 00:06:17 -08:00
fix(thumbnail): [PKG-TOOL-THUMB-07] preserve falsy cleanup exceptions
This commit is contained in:
1 parent
2a38067911
commit
1c522f181b
18 files changed
+5822
-20
No files matched your search
@@ -1,9 +1,43 @@
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { thumbnailCandidate } from '../helpers/thumbnail.js'
|
||||
import { thumbnailCandidate, thumbnailHistorical } from '../helpers/thumbnail.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const candidate = await thumbnailCandidate()
|
||||
|
||||
for (const implementation of [...thumbnailHistorical(), candidate]) {
|
||||
test(`Thumbnail ${implementation.name}: destroy preserves falsy listener exceptions with real DOM cleanup`, async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.addScriptTag({ content: implementation.code })
|
||||
const result = await page.evaluate(() => {
|
||||
return [undefined, null, false, 0, -0, '', Number.NaN].map((failure, index) => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
document.body.append(input)
|
||||
const tool = new window.ArtplayerToolThumbnail({ fileInput: input })
|
||||
let notified = 0
|
||||
let threw = false
|
||||
let same = false
|
||||
tool.on('destroy', () => {
|
||||
notified++
|
||||
throw failure
|
||||
})
|
||||
try {
|
||||
tool.destroy()
|
||||
}
|
||||
catch (error) {
|
||||
threw = true
|
||||
same = Object.is(error, failure)
|
||||
}
|
||||
const videoConnected = tool.video.isConnected
|
||||
input.remove()
|
||||
return { index, threw, same, notified, videoConnected }
|
||||
})
|
||||
})
|
||||
await testInfo.attach('thumbnail-cleanup-errors', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), result }) })
|
||||
expect(result).toEqual(Array.from({ length: 7 }, (_, index) => ({ index, threw: true, same: true, notified: 1, videoConnected: false })))
|
||||
})
|
||||
}
|
||||
|
||||
test('Thumbnail native browser event registry handles special names and nested once without changing DOM cleanup', async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.addScriptTag({ content: candidate.code })
|
||||
@@ -11,7 +45,7 @@ test('Thumbnail native browser event registry handles special names and nested o
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
document.body.append(input)
|
||||
const tool = new ArtplayerToolThumbnail({ fileInput: input })
|
||||
const tool = new window.ArtplayerToolThumbnail({ fileInput: input })
|
||||
const keys = []
|
||||
for (const name of ['__proto__', 'constructor', 'toString', 'hasOwnProperty']) {
|
||||
let calls = 0
|
||||
@@ -23,7 +57,7 @@ test('Thumbnail native browser event registry handles special names and nested o
|
||||
}
|
||||
let nested = false
|
||||
const seen = []
|
||||
tool.on('custom', value => {
|
||||
tool.on('custom', (value) => {
|
||||
if (!nested) {
|
||||
nested = true
|
||||
tool.emit('custom', 'inner')
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Compare cleanup exception identity with frozen releases.
|
||||
import test from 'node:test'
|
||||
import { thumbnailCandidate, thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js'
|
||||
|
||||
const candidate = await thumbnailCandidate()
|
||||
const failures = [undefined, null, false, 0, -0, '', Number.NaN]
|
||||
function attempt(callback) {
|
||||
try {
|
||||
callback()
|
||||
return { threw: false }
|
||||
}
|
||||
catch (error) {
|
||||
return { threw: true, error }
|
||||
}
|
||||
}
|
||||
function create(implementation) {
|
||||
const env = thumbnailEnvironment(implementation)
|
||||
const input = new env.Element('input')
|
||||
const tool = new env.Factory({ fileInput: input })
|
||||
return { ...env, input, tool }
|
||||
}
|
||||
|
||||
test('Thumbnail historical destroy listeners synchronously propagate falsy thrown values', () => {
|
||||
for (const implementation of thumbnailHistorical()) {
|
||||
for (const failure of failures) {
|
||||
const { tool } = create(implementation)
|
||||
tool.on('destroy', () => {
|
||||
throw failure
|
||||
})
|
||||
const result = attempt(() => tool.destroy())
|
||||
assert.equal(result.threw, true, implementation.name)
|
||||
assert(Object.is(result.error, failure), implementation.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail destroy preserves falsy listener exceptions after releasing native resources', () => {
|
||||
for (const failure of failures) {
|
||||
const { tool, input } = create(candidate)
|
||||
let first = 0
|
||||
let second = 0
|
||||
tool.on('destroy', () => {
|
||||
first++
|
||||
throw failure
|
||||
}).on('destroy', () => second++)
|
||||
const result = attempt(() => tool.destroy())
|
||||
assert.equal(result.threw, true)
|
||||
assert(Object.is(result.error, failure))
|
||||
assert.equal(tool.video.parentNode, null)
|
||||
assert.equal(input.handlers.get('change')?.size || 0, 0)
|
||||
assert.equal(first, 1)
|
||||
assert.equal(second, 0, 'Normal emitter exception propagation still stops this dispatch')
|
||||
assert.doesNotThrow(() => tool.destroy())
|
||||
assert.equal(first, 1, 'Failed destroy is still terminal')
|
||||
}
|
||||
})
|
||||
|
||||
test('Thumbnail cleanup retains the first falsy failure and still attempts later cleanup and destroy notification', () => {
|
||||
for (const failure of failures) {
|
||||
const { tool, input, operations } = create(candidate)
|
||||
tool.loadVideo({ name: 'first.mp4', type: 'video/mp4' })
|
||||
const calls = []
|
||||
tool.video.pause = () => {
|
||||
calls.push('pause')
|
||||
throw failure
|
||||
}
|
||||
tool.video.removeAttribute = name => calls.push(`remove:${name}`)
|
||||
tool.video.load = () => {
|
||||
calls.push('load')
|
||||
throw new Error('later decoder reset error')
|
||||
}
|
||||
tool.on('destroy', () => {
|
||||
calls.push('destroy')
|
||||
throw new Error('later listener error')
|
||||
})
|
||||
const result = attempt(() => tool.destroy())
|
||||
assert.equal(result.threw, true)
|
||||
assert(Object.is(result.error, failure), 'Later errors cannot replace the first thrown value')
|
||||
assert.deepEqual(calls, ['pause', 'remove:src', 'load', 'destroy'])
|
||||
assert.equal(tool.video.parentNode, null)
|
||||
assert.equal(input.handlers.get('change')?.size || 0, 0)
|
||||
assert.deepEqual(operations.filter(item => item.name === 'revokeURL').map(item => item.url), [tool.videoUrl])
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user