Files
ArtPlayer/test/helpers/mediabunny.js

103 lines
3.7 KiB
JavaScript

import assert from 'node:assert/strict'
import process from 'node:process'
import vm from 'node:vm'
import { verifyMbContract } from '../../refactor/scripts/mb-contract.mjs'
import { readMember } from '../../refactor/scripts/releases.mjs'
import { browserCandidate } from './browser-candidate.js'
export async function mbCandidate() {
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS)
assert.notEqual(process.env.ARTPLAYER_MB_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
if (process.env.ARTPLAYER_MB_BASELINE === '1')
return (await mbHistorical()).find(item => item.name === 'frozen-workspace')
const candidate = await browserCandidate('artplayer-proxy-mediabunny', process.env.ARTPLAYER_MB_ARTIFACT)
const suffix = { 'installed': 'installed', 'explicit-artifact': 'artifact', 'source-build': 'source' }[candidate.provenance.kind]
return { name: `candidate-${suffix}`, ...candidate }
}
export async function mbBrowserImplementations() {
const candidate = await mbCandidate()
return process.env.ARTPLAYER_MB_BROWSER_CANDIDATE === '1'
? [candidate]
: [...(await mbHistorical()).filter(item => item.name.startsWith('published')), candidate]
}
export async function mbHistorical() {
const { baseline, archives, sources } = await verifyMbContract()
const name = baseline.release.name
return [
...[baseline.release, ...baseline.previous].map(release => ({
name: `published-${release.version}`,
code: readMember(archives.get(release.version), `package/${release.manifest.main.replace(/^\.\//, '')}`).toString(),
})),
{ name: 'frozen-workspace', code: sources.get(`packages/${name}/dist/${name}.js`) },
]
}
export function mbEnvironment({ code }, option = {}) {
const emitted = []
const handlers = new Map()
const frames = new Map()
const canvasListeners = new Map()
let nextFrame = 0
const nativeCalls = []
const context = { clearRect() {}, drawImage() {} }
const canvas = {
width: 300,
height: 150,
style: {},
getContext(type) {
nativeCalls.push(['context', type, this === canvas])
return context
},
addEventListener(type, fn) { canvasListeners.set(type, fn) },
removeEventListener(type) { canvasListeners.delete(type) },
getBoundingClientRect() { return { width: 300, height: 150 } },
setAttribute(name, value) { nativeCalls.push(['attribute', name, value]) },
}
const eventNames = ['volumechange', 'ratechange', 'play', 'pause', 'loadedmetadata', 'error']
const art = {
constructor: { utils: { createElement: () => canvas }, config: { events: eventNames } },
option: { url: '', autoSize: false },
template: { $player: {} },
on(name, fn) {
const listeners = handlers.get(name) || []
listeners.push(fn)
handlers.set(name, listeners)
return this
},
emit(name, ...args) {
emitted.push({ name, args })
for (const fn of handlers.get(name) || []) fn(...args)
return this
},
}
const module = { exports: {} }
const globals = {
module,
exports: module.exports,
window: {},
TextDecoder,
TextEncoder,
ReadableStream,
Blob,
Event,
URL,
AbortController,
queueMicrotask,
console,
performance,
setTimeout: option.setTimeout || setTimeout,
clearTimeout: option.clearTimeout || clearTimeout,
requestAnimationFrame(fn) {
const id = nextFrame++
frames.set(id, fn)
return id
},
cancelAnimationFrame(id) { frames.delete(id) },
}
vm.runInNewContext(code, globals)
const factory = module.exports.default || module.exports
return { factory, exported: module.exports, art, canvas, context, option, frames, nativeCalls, eventNames, handlers, emitted, canvasListeners, globals }
}