mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
163 lines
7.4 KiB
JavaScript
163 lines
7.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Repository Node runner.
|
|
import test from 'node:test'
|
|
import { iframeEnvironment, iframeHistorical } from './helpers/iframe.js'
|
|
|
|
const implementations = (await iframeHistorical()).filter(item => item.global !== 'ArtplayerHelperIframe')
|
|
const flush = () => new Promise(resolve => setImmediate(resolve))
|
|
function observe(promise) {
|
|
const state = { status: 'pending' }
|
|
promise.then(value => Object.assign(state, { status: 'resolved', value }), error => Object.assign(state, { status: 'rejected', error: error.message }))
|
|
return state
|
|
}
|
|
function setup(implementation, injected = true) {
|
|
const env = iframeEnvironment(implementation)
|
|
const instance = new env.Factory({ iframe: new env.Frame(), url: '/first' })
|
|
if (injected)
|
|
env.dispatch({ type: 'inject' })
|
|
return { ...env, instance }
|
|
}
|
|
|
|
for (const implementation of implementations) {
|
|
test(`Iframe ${implementation.name}: same-millisecond requests overwrite a pending callback`, async () => {
|
|
const env = setup(implementation)
|
|
const first = observe(env.instance.postMessage({ type: 'one', data: 1 }))
|
|
const second = observe(env.instance.postMessage({ type: 'two', data: 2 }))
|
|
assert.deepEqual(env.sent.map(item => item.packet.id), [1234, 1234])
|
|
assert.equal(Object.keys(env.instance.promises).length, 1)
|
|
env.dispatch({ type: 'response', data: 'first response', id: 1234 })
|
|
await flush()
|
|
assert.deepEqual(first, { status: 'pending' })
|
|
assert.deepEqual(second, { status: 'resolved', value: 'first response' })
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: destroy retains already-sent pending requests`, async () => {
|
|
const env = setup(implementation)
|
|
const state = observe(env.instance.postMessage({ type: 'one', data: 1 }))
|
|
env.instance.destroy()
|
|
await flush()
|
|
assert.equal(state.status, 'pending')
|
|
assert.deepEqual(Object.keys(env.instance.promises), ['1234'])
|
|
assert.equal(env.handlers.get('message').size, 0)
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: destroy leaves injection polling until the old timer runs`, async () => {
|
|
const env = setup(implementation, false)
|
|
const state = observe(env.instance.postMessage({ type: 'one', data: 1 }))
|
|
assert.equal(env.timers.size, 1)
|
|
env.instance.destroy()
|
|
await flush()
|
|
assert.equal(state.status, 'pending')
|
|
const [id, timer] = [...env.timers][0]
|
|
assert.equal(timer.delay, 200)
|
|
env.timers.delete(id)
|
|
timer.callback()
|
|
await flush()
|
|
assert.deepEqual(state, { status: 'rejected', error: 'The instance has been destroyed' })
|
|
assert.equal(env.timers.size, 0)
|
|
assert.equal(env.sent.length, 0)
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: requests started after destroy reject without a timer`, async () => {
|
|
const env = setup(implementation)
|
|
env.instance.destroy()
|
|
await assert.rejects(env.instance.postMessage({ type: 'late' }), /The instance has been destroyed/)
|
|
assert.equal(env.timers.size, 0)
|
|
assert.equal(env.sent.length, 0)
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: distinct IDs settle out of order and errors retain their message`, async () => {
|
|
const env = setup(implementation)
|
|
const first = observe(env.instance.postMessage({ type: 'one' }))
|
|
env.now(1235)
|
|
const second = observe(env.instance.postMessage({ type: 'two' }))
|
|
env.dispatch({ type: 'error', data: 'remote failure', id: 1235 })
|
|
env.dispatch({ type: 'response', data: 7, id: 1234 })
|
|
await flush()
|
|
assert.deepEqual(first, { status: 'resolved', value: 7 })
|
|
assert.deepEqual(second, { status: 'rejected', error: 'remote failure' })
|
|
assert.equal(Object.keys(env.instance.promises).length, 0)
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: custom non-error response types remain part of the generic protocol`, async () => {
|
|
const env = setup(implementation)
|
|
const result = env.instance.postMessage({ type: 'custom-query' })
|
|
env.dispatch({ type: 'custom-result', data: { ok: true }, id: 1234 })
|
|
assert.deepEqual(await result, { ok: true })
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: malformed message data throws from the public receiver`, () => {
|
|
const env = setup(implementation)
|
|
assert.throws(() => env.dispatch(null), error => error.name === 'TypeError')
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: immediate send failure rejects but leaves its public record`, async () => {
|
|
const env = setup(implementation)
|
|
env.instance.$iframe.contentWindow.postMessage = () => {
|
|
throw new Error('clone failure')
|
|
}
|
|
await assert.rejects(env.instance.postMessage({ type: 'one' }), /clone failure/)
|
|
assert.deepEqual(Object.keys(env.instance.promises), ['1234'])
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: send failure after polling escapes while its promise stays pending`, async () => {
|
|
const env = setup(implementation, false)
|
|
const state = observe(env.instance.postMessage({ type: 'one' }))
|
|
env.dispatch({ type: 'inject' })
|
|
env.instance.$iframe.contentWindow.postMessage = () => {
|
|
throw new Error('late clone failure')
|
|
}
|
|
const [id, timer] = [...env.timers][0]
|
|
env.timers.delete(id)
|
|
assert.throws(() => timer.callback(), /late clone failure/)
|
|
await flush()
|
|
assert.equal(state.status, 'pending')
|
|
assert.deepEqual(Object.keys(env.instance.promises), ['1234'])
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: saved message receiver can still settle and notify after destroy`, async () => {
|
|
const env = setup(implementation)
|
|
const state = observe(env.instance.postMessage({ type: 'one' }))
|
|
const callback = env.instance.onMessage
|
|
let calls = 0
|
|
env.instance.message(() => calls++)
|
|
env.instance.destroy()
|
|
callback({ data: { type: 'response', data: 'late', id: 1234 } })
|
|
await flush()
|
|
assert.deepEqual(state, { status: 'resolved', value: 'late' })
|
|
assert.equal(calls, 1)
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: navigation retains the previous injected state`, () => {
|
|
const env = setup(implementation)
|
|
env.instance.$iframe.src = '/second'
|
|
observe(env.instance.postMessage({ type: 'before-second-inject' }))
|
|
assert.equal(env.instance.injected, true)
|
|
assert.equal(env.sent.length, 1)
|
|
assert.equal(env.timers.size, 0)
|
|
assert.equal(env.instance.url, '/first')
|
|
env.instance.destroy()
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: repeated child inject repeats handshake but deduplicates its DOM listener`, () => {
|
|
const env = iframeEnvironment(implementation, true)
|
|
env.Factory.inject()
|
|
env.Factory.inject()
|
|
assert.deepEqual(env.sent.map(item => item.packet.type), ['inject', 'inject'])
|
|
assert.equal(env.handlers.get('message').size, 1)
|
|
})
|
|
|
|
test(`Iframe ${implementation.name}: child errors send a packet then reject and later commands still run`, async () => {
|
|
const env = iframeEnvironment(implementation, true)
|
|
await assert.rejects(env.Factory.onMessage({ data: { type: 'commit', data: 'throw new Error("child failure")', id: 1 } }), /child failure/)
|
|
await env.Factory.onMessage({ data: { type: 'commit', data: 'return 9', id: 2 } })
|
|
assert.deepEqual(JSON.parse(JSON.stringify(env.sent)), [{ packet: { type: 'error', data: 'child failure', id: 1 }, origin: '*' }, { packet: { type: 'response', data: 9, id: 2 }, origin: '*' }])
|
|
})
|
|
}
|