test(BASE-05): capture published distribution and type consumers

This commit is contained in:
Harvey Zhao committed 2026-09-10 20:34:04 +08:00
1 parent 2b7054d8a5
commit c7a375e146
21 files changed
+5079 -28

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
import Artplayer from 'artplayer'
import french from 'artplayer/i18n/fr'
const art = new Artplayer({ container: '#player', url: 'video.mp4', lang: 'fr', i18n: { fr: french } })
void art
@@ -0,0 +1,3 @@
import chapter from 'artplayer-plugin-chapter/legacy'
const factory = chapter({ chapters: [] })
void factory
@@ -0,0 +1,2 @@
import chapter from 'artplayer-plugin-chapter'
chapter()
+30
View File
@@ -0,0 +1,30 @@
import Artplayer, { type Option, type Setting } from 'artplayer'
import Legacy from 'artplayer/legacy'
import chapter from 'artplayer-plugin-chapter'
const settings: Setting[] = [{ name: 'quality', html: 'Quality', selector: [{ html: 'Auto', default: true }] }]
const option: Option = {
container: '#player', url: 'video.mp4', useSSR: true, settings,
plugins: [chapter({ chapters: [{ start: 0, end: 10, title: 'First' }] })],
}
const art = new Artplayer(option, function (value) {
const self: Artplayer = this
const player: Artplayer = value
self.pause()
player.currentTime = 5
})
const old = new Legacy(option)
const play: Promise<void> = art.play()
const toggle: void = art.toggle()
const html: string = Artplayer.html
const factory = chapter({})
factory(art).update({ chapters: [] })
art.on('custom', (...values: unknown[]) => values.length).off('custom', () => {})
const registration: Promise<Artplayer['plugins']> = art.plugins.add(factory)
void [old, play, toggle, html, registration]
// Keep rejection checks independent of historical declaration mismatches.
// @ts-expect-error url accepts a string
new Artplayer({ container: '#player', url: 42 })
// @ts-expect-error chapter boundaries must be numbers
chapter({ chapters: [{ start: 'zero', end: 1, title: 'Invalid' }] })
+86
View File
@@ -0,0 +1,86 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const vm = require('node:vm')
const { pathToFileURL } = require('node:url')
;(async () => {
const checks = []
const observations = {}
const check = (id, value) => { assert(value, id); checks.push(id) }
const root = __dirname
const load = relative => fs.readFileSync(path.join(root, 'node_modules', relative), 'utf8')
const core = require('artplayer')
const chapter = require('artplayer-plugin-chapter')
check('DIST.cjs', typeof core === 'function' && core.version === '5.4.0' && typeof chapter({}) === 'function')
check('DIST.cjs-legacy', require('artplayer/legacy').version === core.version && typeof require('artplayer-plugin-chapter/legacy')({}) === 'function')
const esm = await import('artplayer')
const pluginEsm = await import('artplayer-plugin-chapter')
check('DIST.esm', esm.default.version === core.version && typeof pluginEsm.default({}) === 'function')
check('DIST.esm-default-only', Object.keys(esm).join() === 'default' && Object.keys(pluginEsm).join() === 'default')
const legacy = await import('artplayer/legacy')
const pluginLegacy = await import('artplayer-plugin-chapter/legacy')
check('DIST.esm-legacy', legacy.default.version === core.version && typeof pluginLegacy.default({}) === 'function')
for (const [name, filename, globalName] of [
['core', 'artplayer/dist/artplayer.js', 'Artplayer'],
['core-legacy', 'artplayer/dist/artplayer.legacy.js', 'Artplayer'],
['chapter', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js', 'artplayerPluginChapter'],
['chapter-legacy', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.legacy.js', 'artplayerPluginChapter'],
]) {
const code = load(filename)
const global = vm.createContext({ console })
vm.runInContext(code, global, { timeout: 2000 })
check(`DIST.global-${name}`, typeof global[globalName] === 'function')
let value
let calls = 0
const define = (factory) => { calls++; value = factory() }
define.amd = {}
const amd = vm.createContext({ console, define })
vm.runInContext(code, amd, { timeout: 2000 })
check(`DIST.amd-${name}`, calls === 1 && typeof value === 'function' && amd[globalName] === value)
}
check('SSR.import-template', core.html.includes('art-video-player') && esm.default.html === core.html && typeof core.STYLE === 'string' && core.instances.length === 0)
observations.constructorErrors = []
for (const Class of [core, esm.default, legacy.default]) {
for (const useSSR of [false, true]) {
let error
try { new Class({ container: '#server', url: 'video.mp4', useSSR }) }
catch (caught) { error = caught }
assert.equal(error?.message, 'Artplayer can only be used in the browser environment')
observations.constructorErrors.push({ useSSR, name: error.name, message: error.message })
}
}
check('SSR.constructor-browser-only', observations.constructorErrors.length === 6)
observations.languages = []
const languages = fs.readdirSync(path.join(root, 'node_modules/artplayer/dist/i18n')).filter(file => file.endsWith('.mjs')).map(file => file.slice(0, -4)).sort()
for (const name of languages) {
const cjs = require(`artplayer/i18n/${name}`)
const module = await import(`artplayer/i18n/${name}`)
assert.deepEqual(module.default, cjs)
const globalName = `artplayerI18n${name.replace(/(^|-)([a-z])/g, (_, _prefix, char) => char.toUpperCase())}`
const context = vm.createContext({})
vm.runInContext(load(`artplayer/dist/i18n/${name}.js`), context, { timeout: 2000 })
assert.equal(JSON.stringify(context[globalName]), JSON.stringify(cjs))
observations.languages.push({ name, globalName, keys: Object.keys(cjs).sort(), play: cjs.Play })
}
check('DIST.i18n-cjs-esm-global', languages.length > 0)
observations.blockedDeepImports = []
for (const specifier of ['artplayer/dist/artplayer.js', 'artplayer/types/artplayer.d.ts', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js']) {
let code
try { require.resolve(specifier) }
catch (error) { code = error.code }
assert.equal(code, 'ERR_PACKAGE_PATH_NOT_EXPORTED')
observations.blockedDeepImports.push({ specifier, code })
}
check('DIST.exports-boundary', observations.blockedDeepImports.length === 3)
observations.resolutions = {}
for (const specifier of ['artplayer', 'artplayer/legacy', 'artplayer-plugin-chapter', 'artplayer-plugin-chapter/legacy']) {
const resolved = require.resolve(specifier)
assert(resolved.startsWith(path.join(root, 'node_modules') + path.sep), 'Escaped isolated packages')
observations.resolutions[specifier] = path.relative(root, resolved).replaceAll('\\', '/')
}
// CDN-style absolute URLs bypass package exports; verify the actual files remain readable imports.
const direct = await import(pathToFileURL(path.join(root, 'node_modules/artplayer/dist/artplayer.mjs')).href)
check('DIST.direct-esm-file', direct.default === esm.default)
process.stdout.write(JSON.stringify({ checks, observations }))
})().catch((error) => { console.error(error); process.exitCode = 1 })
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>ArtPlayer published SSR hydration baseline</title>
<style>.player { width: 480px; height: 270px; } pre { white-space: pre-wrap; }</style>
<h1>Browser reuse of server template</h1>
<button id="run">Run SSR hydration baseline</button>
<p id="status">Ready</p>
<div class="player"></div>
<pre id="result"></pre>
<script src="/releases/artplayer/dist/artplayer.js"></script>
<script src="/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js"></script>
<script src="/fixtures/ssr.js"></script>
</html>
+39
View File
@@ -0,0 +1,39 @@
/* global Artplayer, artplayerPluginChapter */
document.querySelector('#run').onclick = async () => {
const report = { kind: 'ssr', environment: { userAgent: navigator.userAgent, language: navigator.language }, scripts: [...document.scripts].map(script => new URL(script.src).pathname), checks: [], errors: [], unhandled: [] }
const check = (id, passed) => { report.checks.push({ id, passed: Boolean(passed) }); if (!passed) throw new Error(id) }
const onError = event => report.errors.push(event.message)
const onUnhandled = event => report.unhandled.push(String(event.reason))
addEventListener('error', onError)
addEventListener('unhandledrejection', onUnhandled)
let art
try {
const container = document.querySelector('.player')
container.innerHTML = Artplayer.html
const player = container.querySelector('.art-video-player')
const video = container.querySelector('.art-video')
player.dataset.serverMarker = 'preserved'
art = new Artplayer({ container, url: '/assets/sample/video.mp4', useSSR: true, muted: true, plugins: [artplayerPluginChapter({})] })
check('SSR.browser-reuses-player', art.template.$player === player && player.dataset.serverMarker === 'preserved')
check('SSR.browser-reuses-video', art.video === video && container.querySelectorAll('.art-video-player').length === 1)
await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('SSR ready timeout')), 10000)
art.once('ready', () => { clearTimeout(timer); resolve() })
})
check('SSR.browser-ready', art.isReady && video.videoWidth > 0 && video.duration > 0)
check('SSR.browser-plugin', art.plugins.artplayerPluginChapter.name === 'artplayerPluginChapter')
report.observations = { videoWidth: video.videoWidth, duration: video.duration, serverMarker: player.dataset.serverMarker }
art.destroy()
check('SSR.browser-cleanup', container.children.length === 0 && Artplayer.instances.length === 0)
}
catch (error) { report.errors.push(error.message) }
finally {
if (art && Artplayer.instances.includes(art)) art.destroy()
removeEventListener('error', onError)
removeEventListener('unhandledrejection', onUnhandled)
}
document.querySelector('#result').textContent = JSON.stringify(report, null, 2)
const response = await fetch('/reports/ssr', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
document.querySelector('#status').textContent = response.ok ? `Saved ${report.checks.length} checks; errors ${report.errors.length}` : 'Save failed'
document.querySelector('#run').disabled = true
}