test(refactor): [BASE-02] freeze real-browser public API contracts

This commit is contained in:
Harvey Zhao committed 2026-09-10 19:35:55 +08:00
1 parent 833cc11674
commit b382ec08f7
12 files changed
+4034 -22

No files matched your search

+45 -15
View File
@@ -1,9 +1,14 @@
/* global Artplayer, artplayerPluginChapter */
(async () => {
const report = { kind: 'public-api', environment: { userAgent: navigator.userAgent }, checks: [], errors: [] }
const check = (name, result) => {
report.checks.push({ name, passed: Boolean(result) })
if (!result) throw new Error(name)
const report = {
kind: 'public-api',
environment: { userAgent: navigator.userAgent, language: navigator.language },
scripts: [...document.scripts].map(script => new URL(script.src).pathname),
checks: [], errors: [],
}
const check = (id, result) => {
report.checks.push({ id, passed: Boolean(result) })
if (!result) throw new Error(id)
}
function descriptors(object) {
return Object.fromEntries(Object.getOwnPropertyNames(object).sort().map((name) => {
@@ -23,37 +28,57 @@
if (input && typeof input === 'object') return Object.fromEntries(Object.keys(input).sort().map(key => [key, valueOf(input[key])]))
return input
}
function resolvedDescriptors(object) {
const result = new Map()
for (let current = object; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
for (const [name, descriptor] of Object.entries(descriptors(current))) {
if (!result.has(name)) result.set(name, descriptor)
}
}
return Object.fromEntries([...result].sort(([a], [b]) => a.localeCompare(b)))
}
let art
try {
check('published core version', Artplayer.version === '5.4.0')
check('API-01.version', Artplayer.version === '5.4.0')
const defaults = valueOf(Artplayer.option)
check('API-01.defaults-independent', Artplayer.option !== Artplayer.option && Artplayer.option.subtitle !== Artplayer.option.subtitle)
check('API-01.default-language', defaults.lang === navigator.language.toLowerCase())
defaults.lang = { $environment: 'navigator.language.toLowerCase()' }
const constants = Object.fromEntries(Object.getOwnPropertyNames(Artplayer).sort()
.filter(name => /^[A-Z_]+$/.test(name) && name !== 'STYLE')
.map(name => [name, valueOf(Artplayer[name])]))
art = new Artplayer({ container: '.player', url: '/assets/sample/video.mp4', muted: true, plugins: [artplayerPluginChapter()] })
check('constructor registers exact instance', Artplayer.instances.includes(art))
check('native media element exposed', art.video instanceof HTMLVideoElement)
check('chapter registered synchronously', art.plugins.artplayerPluginChapter?.name === 'artplayerPluginChapter')
check('API-01.instance-registration', Artplayer.instances.includes(art))
check('API-01.container-selector', art.template.$container === document.querySelector('.player'))
check('API-02.video', art.video instanceof HTMLVideoElement && art.video === art.template.$video)
const query = art.query
check('API-02.bound-query', query('.art-video') === art.video)
check('API-06.sync-chapter', art.plugins.artplayerPluginChapter?.name === 'artplayerPluginChapter')
const plugin = { name: 'baselineProbe', ping: () => 'pong' }
const added = art.plugins.add(() => plugin)
check('plugins.add returns synchronous registry', added === art.plugins)
check('plugin result methods callable', art.plugins.baselineProbe.ping() === 'pong')
check('API-06.add-return', added === art.plugins)
check('API-06.result-call', art.plugins.baselineProbe.ping() === 'pong')
let received = 0
const handler = amount => { received += amount }
check('on is chainable', art.on('baseline:event', handler) === art)
check('API-04.on-return', art.on('baseline:event', handler) === art)
art.emit('baseline:event', 3)
art.off('baseline:event', handler)
art.emit('baseline:event', 7)
check('custom event on/emit/off', received === 3)
check('API-04.off', received === 3)
const integrations = {}
for (const name of ['template', 'events', 'controls', 'setting', 'contextmenu', 'layers', 'plugins', 'storage', 'subtitle', 'i18n']) {
integrations[name] = { own: descriptors(art[name]), prototype: descriptors(Object.getPrototypeOf(art[name])) }
integrations[name] = { own: descriptors(art[name]), prototype: descriptors(Object.getPrototypeOf(art[name])), resolved: resolvedDescriptors(art[name]) }
}
report.snapshot = {
static: descriptors(Artplayer),
prototype: descriptors(Artplayer.prototype),
emitterPrototype: descriptors(Object.getPrototypeOf(Artplayer.prototype)),
defaults,
constants,
config: valueOf(Artplayer.config),
utilityDescriptors: descriptors(Artplayer.utils),
instance: descriptors(art),
instanceResolved: resolvedDescriptors(art),
chapterFactory: descriptors(artplayerPluginChapter),
chapterResult: descriptors(art.plugins.artplayerPluginChapter),
integrations,
@@ -64,8 +89,13 @@
}
finally {
if (art) {
art.destroy(true)
report.checks.push({ name: 'instance cleanup', passed: !Artplayer.instances.includes(art) })
try {
check('API-05.destroy-return', art.destroy(true) === undefined)
check('API-05.instance-cleanup', !Artplayer.instances.includes(art))
}
catch (error) {
report.errors.push(error.message)
}
}
}
const response = await fetch('/reports/api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })