mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
268 lines
10 KiB
JavaScript
268 lines
10 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Candidate SDK lifecycle acceptance uses Node's runner.
|
|
import test from 'node:test'
|
|
import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js'
|
|
|
|
const candidates = (await vastImplementations()).filter(item => !item.historical && !item.published)
|
|
|
|
for (const implementation of candidates) {
|
|
const { name } = implementation
|
|
test(`${name}: core destruction releases SDK and makes escaped methods inert`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
const host = env.host()
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
value.playUrl('/first.xml')
|
|
}, implementation.options)(host.art)
|
|
const player = context.imaPlayer
|
|
host.art.destroy()
|
|
result.destroy()
|
|
assert.equal(context.init(), null)
|
|
context.playUrl('/late.xml')
|
|
context.playRes('late response')
|
|
assert.equal(player.destroyCalls, 1)
|
|
assert.equal(player.requests.length, 1)
|
|
assert.equal(host.parent.children.length, 0)
|
|
assert.equal(context.imaPlayer, null)
|
|
assert.equal(context.container, null)
|
|
})
|
|
|
|
test(`${name}: destruction while SDK is loading suppresses callback and allocation`, async () => {
|
|
const env = vastEnvironment(implementation, { deferLoad: true })
|
|
const host = env.host()
|
|
let calls = 0
|
|
const attaching = env.factory(() => calls++, implementation.options)(host.art)
|
|
assert.equal(host.listeners.get('destroy').size, 1)
|
|
host.art.destroy()
|
|
env.resolveLoad()
|
|
const result = await attaching
|
|
result.destroy()
|
|
assert.equal(calls, 0)
|
|
assert.equal(env.state.players.length, 0)
|
|
assert.equal(host.parent.children.length, 0)
|
|
})
|
|
|
|
test(`${name}: already destroyed hosts do not load SDK or subscribe`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
const host = env.host()
|
|
host.art.destroy()
|
|
await env.factory(() => assert.fail('Unexpected callback'), implementation.options)(host.art)
|
|
assert.equal(env.state.loads, 0)
|
|
assert.equal(host.listeners.size, 0)
|
|
})
|
|
|
|
test(`${name}: SDK rejection unregisters the pending core listener`, async () => {
|
|
const env = vastEnvironment(implementation, { deferLoad: true })
|
|
const host = env.host()
|
|
const attaching = env.factory(undefined, implementation.options)(host.art)
|
|
const failure = new Error('load failure')
|
|
const rejected = assert.rejects(attaching, error => error === failure)
|
|
env.rejectLoad(failure)
|
|
await rejected
|
|
assert.equal(host.listeners.get('destroy').size, 0)
|
|
})
|
|
|
|
test(`${name}: callback rejection reclaims its SDK and preserves the original error over cleanup failure`, async () => {
|
|
const failure = new Error('callback failure')
|
|
for (const destroyError of [null, new Error('cleanup failure')]) {
|
|
const env = vastEnvironment(implementation, { destroyError })
|
|
const host = env.host()
|
|
let context
|
|
await assert.rejects(env.factory((value) => {
|
|
context = value
|
|
context.init()
|
|
throw failure
|
|
}, implementation.options)(host.art), error => error === failure)
|
|
assert.equal(env.state.players[0].destroyCalls, 1)
|
|
assert.equal(host.parent.children.length, 0)
|
|
assert.equal(host.listeners.get('destroy').size, 0)
|
|
assert.equal(context.init(), null)
|
|
if (destroyError)
|
|
assert.equal(env.state.errors[0][1], destroyError)
|
|
}
|
|
})
|
|
|
|
test(`${name}: a callback resumed after core destroy cannot request or recreate ads`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
const host = env.host()
|
|
const started = deferred()
|
|
const finish = deferred()
|
|
const attaching = env.factory(async (context) => {
|
|
context.init()
|
|
started.resolve()
|
|
await finish.promise
|
|
context.playUrl('/late.xml')
|
|
assert.equal(context.init(), null)
|
|
}, implementation.options)(host.art)
|
|
await started.promise
|
|
host.art.destroy()
|
|
finish.resolve()
|
|
await attaching
|
|
assert.equal(env.state.players.length, 1)
|
|
assert.equal(env.state.players[0].destroyCalls, 1)
|
|
assert.equal(env.state.players[0].requests.length, 0)
|
|
assert.equal(host.parent.children.length, 0)
|
|
})
|
|
|
|
test(`${name}: constructor and event-registration failures roll back and allow explicit retry`, async () => {
|
|
for (const field of ['constructError', 'listenError']) {
|
|
const failure = new Error(field)
|
|
const env = vastEnvironment(implementation, { [field]: failure })
|
|
const host = env.host()
|
|
let context
|
|
await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(host.art)
|
|
assert.throws(() => context.init(), error => error === failure)
|
|
assert.equal(host.parent.children.length, 0)
|
|
assert.equal(context.imaPlayer, null)
|
|
assert.equal(context.container, null)
|
|
if (field === 'listenError')
|
|
assert.equal(env.state.players[0].destroyCalls, 1)
|
|
env.state[field] = null
|
|
const player = context.init()
|
|
assert.equal(player.listeners.size, 4)
|
|
assert.equal(host.parent.children.length, 1)
|
|
}
|
|
})
|
|
|
|
test(`${name}: same-millisecond instances use distinct IDs and clean up independently`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
const contexts = []
|
|
const attach = env.factory((context) => {
|
|
context.init()
|
|
contexts.push(context)
|
|
}, implementation.options)
|
|
const one = env.host()
|
|
const two = env.host()
|
|
const [result] = await Promise.all([attach(one.art), attach(two.art)])
|
|
assert.notEqual(contexts[0].container.id, contexts[1].container.id)
|
|
result.destroy()
|
|
assert.equal(one.parent.children.length, 0)
|
|
assert.equal(two.parent.children.length, 1)
|
|
contexts[1].playUrl('/other.xml')
|
|
assert.equal(contexts[1].imaPlayer.requests.length, 1)
|
|
})
|
|
|
|
test(`${name}: explicit destroy during an active ad clears suppression for a new session`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(env.host().art)
|
|
context.playUrl('/first.xml')
|
|
context.imaPlayer.emit('AdStarted')
|
|
result.destroy()
|
|
context.playUrl('/second.xml')
|
|
assert.equal(env.state.players.length, 2)
|
|
assert.equal(context.imaPlayer.requests[0].adTagUrl, '/second.xml')
|
|
})
|
|
|
|
test(`${name}: saved old SDK callbacks are inert both after cleanup and after recreation`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(env.host().art)
|
|
const first = context.init()
|
|
const late = [...first.listeners.values()].flatMap(listeners => [...listeners])
|
|
result.destroy()
|
|
for (const callback of late) assert.doesNotThrow(() => callback({ detail: 'late' }))
|
|
context.init()
|
|
for (const callback of late) callback({ detail: 'late' })
|
|
assert.equal(context.container.style.display, 'none')
|
|
context.playUrl('/fresh.xml')
|
|
assert.equal(context.imaPlayer.requests.length, 1)
|
|
assert.equal(env.state.errors.length, 0)
|
|
})
|
|
|
|
test(`${name}: SDK destroy failure still clears references and DOM and is not retried accidentally`, async () => {
|
|
const failure = new Error('destroy failure')
|
|
const env = vastEnvironment(implementation, { destroyError: failure })
|
|
const host = env.host()
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(host.art)
|
|
const player = context.init()
|
|
assert.throws(() => result.destroy(), error => error === failure)
|
|
assert.equal(context.container, null)
|
|
assert.equal(context.imaPlayer, null)
|
|
assert.equal(host.parent.children.length, 0)
|
|
assert.doesNotThrow(() => result.destroy())
|
|
assert.equal(player.destroyCalls, 1)
|
|
env.state.destroyError = null
|
|
assert.notEqual(context.init(), player)
|
|
})
|
|
|
|
test(`${name}: a request config accessor destroying the core prevents playAds`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
const host = env.host()
|
|
let context
|
|
await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(host.art)
|
|
context.playUrl('/cancelled.xml', { get extra() {
|
|
host.art.destroy()
|
|
return 1
|
|
} })
|
|
assert.equal(env.state.players[0].requests.length, 0)
|
|
assert.equal(env.state.players[0].destroyCalls, 1)
|
|
assert.equal(host.parent.children.length, 0)
|
|
})
|
|
|
|
test(`${name}: a request accessor replacing the session cannot dispatch through its stale player`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(env.host().art)
|
|
context.playUrl('/stale.xml', { get extra() {
|
|
result.destroy()
|
|
context.init()
|
|
return 1
|
|
} })
|
|
assert.equal(env.state.players.length, 2)
|
|
assert.equal(env.state.players[0].requests.length, 0)
|
|
assert.equal(env.state.players[1].requests.length, 0)
|
|
context.playUrl('/fresh.xml')
|
|
assert.equal(env.state.players[1].requests[0].adTagUrl, '/fresh.xml')
|
|
})
|
|
|
|
test(`${name}: destruction reentered from SDK construction or subscription reclaims late resources`, async () => {
|
|
for (const hook of ['onConstruct', 'onListen']) {
|
|
const env = vastEnvironment(implementation)
|
|
const host = env.host()
|
|
let context
|
|
await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(host.art)
|
|
env.state[hook] = () => host.art.destroy()
|
|
assert.equal(context.init(), null)
|
|
assert.equal(env.state.players[0].destroyCalls, 1)
|
|
assert.equal(host.parent.children.length, 0)
|
|
assert.equal(context.imaPlayer, null)
|
|
}
|
|
})
|
|
|
|
test(`${name}: SDK destroy reentrancy cannot create a replacement until cleanup finishes`, async () => {
|
|
const env = vastEnvironment(implementation)
|
|
let context
|
|
const result = await env.factory((value) => {
|
|
context = value
|
|
}, implementation.options)(env.host().art)
|
|
context.init()
|
|
env.state.onDestroy = () => {
|
|
assert.equal(context.init(), null)
|
|
context.playUrl('/during-cleanup.xml')
|
|
}
|
|
result.destroy()
|
|
assert.equal(env.state.players.length, 1)
|
|
env.state.onDestroy = null
|
|
context.playUrl('/after-cleanup.xml')
|
|
assert.equal(env.state.players.length, 2)
|
|
assert.equal(context.imaPlayer.requests.length, 1)
|
|
})
|
|
}
|