test(iframe): [PKG-IFRAME-01] freeze historical exports and message contract

This commit is contained in:
Harvey Zhao committed 2026-09-12 23:00:01 +08:00
1 parent 7e37fd4d0e
commit a52f79a0cc
12 files changed
+505 -8

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
export async function verifyIframeContract() {
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/iframe-release.json')))
assert.equal(baseline.registryObservation.status, 404)
assert.equal(baseline.release.name, 'artplayer-plugin-iframe')
const archive = await ensureArchive(baseline.release)
assert.deepEqual(archiveFiles(archive), Object.keys(baseline.release.files).sort())
for (const [file, expected] of Object.entries(baseline.release.files))
assert.equal(hash(readMember(archive, file)), expected, file)
assert.deepEqual(JSON.parse(readMember(archive, 'package/package.json')), baseline.release.manifest)
const core = baseline.release.historicalCore
const coreSource = execFileSync('git', ['show', `${core.commit}:${core.file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n')
assert.equal(hash(coreSource), core.sha256LF)
assert.equal(JSON.parse(coreSource).version, core.version)
const sources = new Map()
for (const [file, expected] of Object.entries(baseline.source)) {
const source = execFileSync('git', ['show', `${baseline.sourceCommit}:${file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n')
assert.equal(hash(source), expected, file)
sources.set(file, source)
}
return { baseline, archive, sources }
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const { baseline } = await verifyIframeContract()
console.log(`Iframe contract: historical ${baseline.release.name}@${baseline.release.version}, ${Object.keys(baseline.release.files).length} archive members, ${Object.keys(baseline.source).length} frozen workspace inputs; tool-name registry 404 remains an observation`)
}
+136
View File
@@ -0,0 +1,136 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Frozen package contract runner.
import test from 'node:test'
import vm from 'node:vm'
import { iframeEnvironment, iframeHistorical } from '../../test/helpers/iframe.js'
import { verifyIframeContract } from './iframe-contract.mjs'
import { readMember } from './releases.mjs'
test('Iframe actual old-name archive and renamed workspace remain separate provenance', async () => {
const { baseline, archive, sources } = await verifyIframeContract()
assert.equal(Object.keys(baseline.release.files).length, 8)
assert.equal(Object.keys(baseline.source).length, 11)
assert.equal(baseline.release.manifest.module, undefined)
assert.equal(baseline.registryObservation.body.error, 'Not found')
const old = readMember(archive, 'package/types/artplayer-plugin-iframe.d.ts').toString()
const current = sources.get('packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.ts')
assert.match(old, /export = ArtplayerPluginIframe/)
assert.match(old, /export as namespace ArtplayerPluginIframe/)
assert.match(current, /export default ArtplayerToolIframe/)
for (const source of [old, current]) {
assert.match(source, /resove:/)
assert.match(source, /Promise<ReturnType<T>>/)
assert.match(source, /static onMessage\(event: MessageEvent & \{ data: Message \}\): void/)
}
const esm = await import(`data:text/javascript,${encodeURIComponent(sources.get('packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs'))}`)
assert.equal(typeof esm.default, 'function')
assert.deepEqual(Object.keys(esm), ['default'])
})
const implementations = await iframeHistorical()
for (const implementation of implementations.filter(item => item.global !== 'ArtplayerHelperIframe')) {
test(`Iframe ${implementation.name}: class, CJS/script exports and constructor fields`, () => {
const env = iframeEnvironment(implementation)
assert.equal(typeof env.exported, implementation.namespace ? 'object' : 'function')
const browser = {}
browser.window = browser
browser.self = browser
vm.runInNewContext(implementation.code, browser)
assert.equal(typeof browser[implementation.global], 'function')
assert.deepEqual(Object.getOwnPropertyNames(env.Factory.prototype), ['constructor', 'onMessage', 'postMessage', 'commit', 'message', 'destroy'])
assert.throws(() => new env.Factory({ iframe: {}, url: '/' }), error => error.name === (implementation.namespace ? 'Error' : 'TypeError') && /needs to be a HTMLIFrameElement/.test(error.message))
assert.throws(() => new env.Factory({ iframe: new env.Frame(), url: null }), /needs to be a string/)
const frame = new env.Frame()
const instance = new env.Factory({ iframe: frame, url: '/iframe.html' })
assert.deepEqual(Object.keys(instance), ['url', '$iframe', 'promises', 'injected', 'destroyed', 'messageCallback', 'onMessage'])
assert.deepEqual(env.order, ['listen:message', 'src'])
assert.equal(instance.$iframe, frame)
assert.equal(instance.url, frame.url)
assert.equal(instance.injected, false)
assert.equal(instance.destroyed, false)
assert.equal(instance.messageCallback(), null)
assert.throws(() => instance.commit('return 1'), /needs to be a function/)
assert.throws(() => instance.message(null), /needs to be a function/)
assert.equal(instance.destroy(), undefined)
assert.equal(env.handlers.get('message').size, 0)
})
test(`Iframe ${implementation.name}: numeric envelopes, resove spelling and callback receiver`, async () => {
const env = iframeEnvironment(implementation)
const instance = new env.Factory({ iframe: new env.Frame(), url: '/iframe.html' })
const observed = []
assert.equal(instance.message(function (message) {
observed.push({ receiver: this === instance, ...message })
}), undefined)
env.dispatch({ type: 'inject' })
assert.equal(instance.injected, true)
const pending = instance.postMessage({ type: 'query', data: 3, id: 999 })
assert.equal(typeof pending.then, 'function')
assert.deepEqual(JSON.parse(JSON.stringify(env.sent)), [{ packet: { type: 'query', data: 3, id: 1234 }, origin: '*' }])
assert.deepEqual(Object.keys(instance.promises[1234]), ['resove', 'reject'])
env.dispatch({ type: 'response', data: 4, id: 1234 })
assert.equal(await pending, 4)
assert.equal(Object.keys(instance.promises).length, 0)
assert.deepEqual(observed, [{ receiver: true, type: 'inject', data: undefined }, { receiver: true, type: 'response', data: 4 }])
instance.destroy()
})
test(`Iframe ${implementation.name}: child injection and synchronous commit response contract`, async () => {
const parent = iframeEnvironment(implementation)
assert.equal(parent.Factory.iframe, false)
assert.throws(() => parent.Factory.inject(), /can only be used in iframe/)
const env = iframeEnvironment(implementation, true)
assert.equal(env.Factory.iframe, true)
assert.equal(env.Factory.inject(), undefined)
assert.deepEqual(JSON.parse(JSON.stringify(env.sent)), [{ packet: { type: 'inject', id: 0 }, origin: '*' }])
await env.Factory.onMessage({ data: { type: 'commit', data: 'return 2 + 3', id: 17 } })
assert.deepEqual(JSON.parse(JSON.stringify(env.sent.at(-1))), { packet: { type: 'response', data: 5, id: 17 }, origin: '*' })
})
test(`Iframe ${implementation.name}: commit serializes a body and resolver replies remain asynchronous`, async () => {
const env = iframeEnvironment(implementation)
const instance = new env.Factory({ iframe: new env.Frame(), url: '/' })
env.dispatch({ type: 'inject' })
const pending = instance.commit(() => {
return 7
})
const packet = env.sent.at(-1).packet
assert.equal(packet.type, 'commit')
assert.equal(packet.data.trim(), 'return 7')
assert.equal(packet.id, 1234)
env.dispatch({ type: 'response', data: 7, id: packet.id })
assert.equal(await pending, 7)
const child = iframeEnvironment(implementation, true)
const completion = child.Factory.onMessage({ data: { type: 'commit', data: 'resolve(8)', id: 18 } })
assert.equal(typeof completion.then, 'function')
await completion
assert.deepEqual(JSON.parse(JSON.stringify(child.sent.at(-1))), { packet: { type: 'response', data: 8, id: 18 }, origin: '*' })
instance.destroy()
})
}
for (const implementation of implementations.filter(item => item.global === 'ArtplayerHelperIframe')) {
test(`Iframe ${implementation.name}: extra helper artifact has a distinct static destroy protocol`, async () => {
const env = iframeEnvironment(implementation)
assert.equal(typeof env.exported, 'object')
assert.equal(env.Factory.iframe, undefined)
assert.equal(typeof env.Factory.destroy, 'function')
const browser = {}
browser.window = browser
browser.self = browser
vm.runInNewContext(implementation.code, browser)
assert.equal(typeof browser.ArtplayerHelperIframe, 'function')
const instance = new env.Factory({ iframe: new env.Frame(), url: '/' })
assert.equal(instance.isInject, false)
assert.equal(instance.isDestroy, false)
assert.equal(instance.injected, undefined)
env.Factory.inject()
assert.equal(env.Factory.isInject, true)
await env.Factory.onMessage({ data: { type: 'destroy' } })
assert.equal(env.Factory.isDestroy, true)
assert.equal(env.Factory.isInject, false)
assert.deepEqual(JSON.parse(JSON.stringify(env.sent)), [{ packet: { type: 'inject', id: 0 }, origin: '*' }, { packet: { type: 'destroy', id: 0 }, origin: '*' }])
instance.destroy()
assert.equal(instance.isDestroy, true)
})
}