mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
151 lines
6.0 KiB
JavaScript
151 lines
6.0 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Use the repository Node runner.
|
|
import { test } from 'node:test'
|
|
import vm from 'node:vm'
|
|
import { loadModules } from './helpers/load.js'
|
|
|
|
const lifecycle = 'packages/artplayer/src/lifecycle/instance'
|
|
const { Plugins, beginLifecycle, getScope, destroyInstance } = await loadModules({
|
|
Plugins: 'packages/artplayer/src/plugins/index',
|
|
...Object.fromEntries(['beginLifecycle', 'getScope', 'destroyInstance'].map(name => [name, { file: lifecycle, name }])),
|
|
})
|
|
|
|
function create(plugins = []) {
|
|
const art = { option: { miniProgressBar: false, isLive: false, lock: false, autoPlayback: false, autoOrientation: false, fastForward: false, plugins } }
|
|
beginLifecycle(art)
|
|
return { art, registry: new Plugins(art) }
|
|
}
|
|
|
|
test('plugin registration keeps receiver, arguments, id, descriptors and naming precedence', () => {
|
|
const { art, registry } = create()
|
|
const result = { name: 'returnedName' }
|
|
function factory(value) {
|
|
assert.equal(this, art)
|
|
assert.equal(value, art)
|
|
assert.equal(arguments.length, 1)
|
|
return result
|
|
}
|
|
assert.equal(registry.add(factory), registry)
|
|
assert.equal(registry.returnedName, result)
|
|
assert.equal(registry.id, 1)
|
|
assert.deepEqual(Object.getOwnPropertyDescriptor(registry, 'returnedName'), { value: result, writable: false, enumerable: false, configurable: false })
|
|
function named() {}
|
|
assert.equal(registry.add(named), registry)
|
|
assert(Object.hasOwn(registry, 'named'))
|
|
registry.add((0, () => null))
|
|
assert.equal(registry.plugin3, null)
|
|
assert.throws(() => registry.add(() => result), /Cannot add a plugin that already has the same name: returnedName/)
|
|
assert.equal(registry.id, 4)
|
|
assert.deepEqual(Object.keys(registry), ['art', 'id'])
|
|
})
|
|
|
|
test('async factories preserve pending visibility, completion-time fallback and registry fulfillment', async () => {
|
|
const { registry } = create()
|
|
let resolve
|
|
const result = registry.add((0, () => new Promise(done => resolve = done)))
|
|
assert(result instanceof Promise)
|
|
assert(!Object.hasOwn(registry, 'plugin1'))
|
|
registry.add(() => ({ name: 'second' }))
|
|
resolve(undefined)
|
|
assert.equal(await result, registry)
|
|
assert(Object.hasOwn(registry, 'plugin2'))
|
|
assert(!Object.hasOwn(registry, 'plugin1'))
|
|
const failure = new Error('factory failure')
|
|
await assert.rejects(registry.add(() => Promise.reject(failure)), error => error === failure)
|
|
assert.throws(() => registry.add(() => {
|
|
throw failure
|
|
}), error => error === failure)
|
|
})
|
|
|
|
test('only same-realm Promises are awaited, preserving catch-less thenables and foreign promises', () => {
|
|
const { registry } = create()
|
|
const thenable = { name: 'thenable', then() {
|
|
throw new Error('must not assimilate')
|
|
} }
|
|
assert.equal(registry.add(() => thenable), registry)
|
|
assert.equal(registry.thenable, thenable)
|
|
const foreign = vm.runInNewContext('Promise.resolve({ name: "foreignResult" })')
|
|
const foreignFactory = () => foreign
|
|
assert.equal(registry.add(foreignFactory), registry)
|
|
assert.equal(registry.foreignFactory, foreign)
|
|
})
|
|
|
|
test('closed registries ignore late results without inspecting or destroying them and reject new factories', async () => {
|
|
const { art, registry } = create()
|
|
let resolve
|
|
const pending = registry.add(() => new Promise(done => resolve = done))
|
|
getScope(art).dispose()
|
|
resolve({ get name() {
|
|
throw new Error('late result must not be inspected')
|
|
}, destroy() {
|
|
throw new Error('not a lifecycle hook')
|
|
} })
|
|
assert.equal(await pending, registry)
|
|
assert.deepEqual(Object.getOwnPropertyNames(registry), ['art', 'id'])
|
|
assert.throws(() => registry.add(() => {
|
|
throw new Error('factory must not run')
|
|
}), /Cannot add a plugin after ArtPlayer is destroyed/)
|
|
assert.equal(registry.id, 1)
|
|
})
|
|
|
|
test('reentrant destruction in a name getter or property-key coercion cannot publish a result', () => {
|
|
for (const closeAt of [0, 1, 2, 3]) {
|
|
const { art, registry } = create()
|
|
const coercions = []
|
|
const name = { [Symbol.toPrimitive](hint) {
|
|
coercions.push(hint)
|
|
if (coercions.length === closeAt)
|
|
getScope(art).dispose()
|
|
return 'closing'
|
|
} }
|
|
const result = closeAt > 0
|
|
? { name }
|
|
: { get name() {
|
|
getScope(art).dispose()
|
|
return 'closing'
|
|
} }
|
|
assert.equal(registry.add(() => result), registry)
|
|
assert(!Object.hasOwn(registry, 'closing'))
|
|
assert.deepEqual(coercions, Array.from({ length: closeAt }, () => 'string'))
|
|
}
|
|
})
|
|
|
|
test('live key coercion order and symbol-name failure retain native behavior', () => {
|
|
const { registry } = create()
|
|
const calls = []
|
|
const result = { name: { [Symbol.toPrimitive](hint) {
|
|
calls.push(hint)
|
|
return `key${calls.length}`
|
|
} } }
|
|
registry.add(() => result)
|
|
assert.equal(registry.key3, result)
|
|
assert.deepEqual(calls, ['string', 'string', 'string'])
|
|
assert.throws(() => registry.add(() => ({ name: Symbol('name') })), TypeError)
|
|
})
|
|
|
|
test('constructor stops after a synchronous close and observes its own async rejection', async (context) => {
|
|
const failure = new Error('initial async failure')
|
|
const warnings = []
|
|
context.mock.method(console, 'warn', (...args) => warnings.push(args))
|
|
const { art } = create([() => Promise.reject(failure), function close() {
|
|
getScope(this).dispose()
|
|
}, () => {
|
|
throw new Error('must not start')
|
|
}])
|
|
assert(getScope(art).closed)
|
|
await new Promise(resolve => setImmediate(resolve))
|
|
assert.deepEqual(warnings, [['Failed to initialize ArtPlayer plugin:', failure]])
|
|
})
|
|
|
|
test('registration during reset is rejected before resource scope disposal', () => {
|
|
const { art, registry } = create()
|
|
Object.assign(art, { isDestroy: false, reset() {
|
|
assert.equal(getScope(art).closed, false)
|
|
assert.throws(() => registry.add(() => {
|
|
throw new Error('must not execute during reset')
|
|
}), /Cannot add a plugin after ArtPlayer is destroyed/)
|
|
}, template: { $video: {}, destroy() {} }, emit() {} })
|
|
destroyInstance(art, [art], true, true)
|
|
assert.equal(registry.id, 0)
|
|
})
|