mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-16 09:36:16 -08:00
test(vtt-thumbnail): [PKG-VTT-THUMB-01] freeze published runtime contracts
This commit is contained in:
1 parent
8c57d56f17
commit
d4ede650e8
15 files changed
+1003
-7
No files matched your search
@@ -44,7 +44,8 @@ function fixture(t) {
|
||||
test('Actual contract model assigns all 264 family rows and separates exact published versions from workspace snapshots', () => {
|
||||
assert.equal(actual.policy.packages.length, 22)
|
||||
assert.equal(actual.families.length, 12)
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 20)
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 25)
|
||||
assert.deepEqual(actual.policy.versions.filter(version => version.kind === 'published-baseline' && version.package === 'artplayer-plugin-vtt-thumbnail').map(version => version.version).sort(), ['1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.1.0'])
|
||||
assert.deepEqual(actual.policy.versions.filter(version => version.kind === 'published-baseline' && version.package === 'artplayer-plugin-auto-thumbnail').map(version => version.version).sort(), ['1.0.0', '1.0.1', '1.1.0'])
|
||||
assert.deepEqual(actual.policy.versions.filter(version => version.kind === 'published-baseline' && version.package === 'artplayer-proxy-mediabunny').map(version => version.version).sort(), ['1.0.0', '1.2.0'])
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'workspace-baseline').length, 22)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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 vm from 'node:vm'
|
||||
import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
export async function verifyVttThumbnailContract() {
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/vtt-thumbnail-release.json'), 'utf8'))
|
||||
const archives = new Map()
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
const archive = await ensureArchive(release)
|
||||
assert.deepEqual(archiveFiles(archive), Object.keys(release.files).sort())
|
||||
for (const [file, expected] of Object.entries(release.files))
|
||||
assert.equal(hash(readMember(archive, file)), expected, `${release.version}/${file}`)
|
||||
assert.deepEqual(JSON.parse(readMember(archive, 'package/package.json')), release.manifest)
|
||||
const missing = Object.fromEntries(['main', 'module', 'types', 'legacy'].filter(field => release.manifest[field] && !Object.hasOwn(release.files, `package/${release.manifest[field].replace(/^\.\//, '')}`)).map(field => [field, release.manifest[field]]))
|
||||
assert.deepEqual(missing, release.missingEntrypoints)
|
||||
if (release.version === '1.0.0') {
|
||||
for (const field of ['main', 'legacy']) {
|
||||
const code = readMember(archive, `package/${release.manifest[field].replace(/^\.\//, '')}`).toString()
|
||||
assert.throws(() => new vm.Script(code), error => error.name === 'SyntaxError' && /Invalid regular expression/.test(error.message), 'The actual 1.0.0 bundles cannot parse; never replace them with source-only fixtures')
|
||||
}
|
||||
}
|
||||
const core = release.historicalCore
|
||||
const source = execFileSync('git', ['show', `${core.commit}:${core.file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n')
|
||||
assert.equal(hash(source), core.sha256LF)
|
||||
assert.equal(JSON.parse(source).version, core.version)
|
||||
archives.set(release.version, archive)
|
||||
}
|
||||
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, `VTT-thumbnail frozen source: ${file}`)
|
||||
sources.set(file, source)
|
||||
}
|
||||
return { baseline, archives, sources }
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
await verifyVttThumbnailContract()
|
||||
console.log('VTT-thumbnail verified: 5 actual npm archives, 34 members, 10 frozen inputs; 1.0.0 main/legacy syntax failures remain explicit')
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Node frozen release-contract runner.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
import { acceptedVttText, vttText, vttThumbnailEnvironment, vttThumbnailHistorical } from '../../test/helpers/vtt-thumbnail.js'
|
||||
import { readMember } from './releases.mjs'
|
||||
import { verifyVttThumbnailContract } from './vtt-thumbnail-contract.mjs'
|
||||
|
||||
const contract = await verifyVttThumbnailContract()
|
||||
const implementations = await vttThumbnailHistorical()
|
||||
const name = 'artplayer-plugin-vtt-thumbnail'
|
||||
|
||||
test('VTT-thumbnail freezes five actual archives, 34 members, ten source inputs and separate Git core associations', () => {
|
||||
const releases = [contract.baseline.release, ...contract.baseline.previous]
|
||||
assert.deepEqual(releases.map(release => release.version), ['1.1.0', '1.0.0', '1.0.1', '1.0.2', '1.0.3'])
|
||||
assert.equal(releases.reduce((sum, release) => sum + Object.keys(release.files).length, 0), 34)
|
||||
assert.equal(Object.keys(contract.baseline.source).length, 10)
|
||||
assert.deepEqual(releases.map(release => release.historicalCore.version), ['5.3.1', '5.1.2', '5.1.6', '5.1.7', '5.1.7'])
|
||||
for (const release of releases) {
|
||||
assert.deepEqual(release.missingEntrypoints, {})
|
||||
assert.equal(release.manifest.dependencies, undefined)
|
||||
assert.equal(release.manifest.peerDependencies, undefined)
|
||||
}
|
||||
})
|
||||
|
||||
test('VTT-thumbnail 1.0.0 actual main and legacy cannot parse; its valid shipped source stays source-only', () => {
|
||||
const release = contract.baseline.previous[0]
|
||||
for (const field of ['main', 'legacy']) {
|
||||
const code = readMember(contract.archives.get('1.0.0'), `package/${release.manifest[field].replace(/^\.\//, '')}`).toString()
|
||||
assert.throws(() => new vm.Script(code), error => error.name === 'SyntaxError' && /Nothing to repeat/.test(error.message))
|
||||
assert(code.includes('(?:?-->?)'))
|
||||
}
|
||||
const source = readMember(contract.archives.get('1.0.0'), 'package/src/getVttArray.js').toString()
|
||||
assert(source.includes('(?: ?--> ?)'))
|
||||
assert(implementations.some(item => item.name === 'published-1.0.0-source-only'))
|
||||
assert(!implementations.some(item => item.name === 'published-1.0.0-main'))
|
||||
})
|
||||
|
||||
test('VTT-thumbnail historical declarations omit Promise while retaining optional vtt/style and required option object', () => {
|
||||
for (const release of [contract.baseline.release, ...contract.baseline.previous]) {
|
||||
const source = readMember(contract.archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`).toString()
|
||||
assert.match(source, /\(option: \{ vtt\?: string/)
|
||||
assert.match(source, /style\?: Partial<CSSStyleDeclaration>/)
|
||||
assert.match(source, /name: ['"]artplayerPluginVttThumbnail['"]/)
|
||||
assert.doesNotMatch(source, /Promise</)
|
||||
assert.match(source, release.version === '1.1.0' ? /export default artplayerPluginVttThumbnail/ : /export = artplayerPluginVttThumbnail/)
|
||||
if (release.version !== '1.1.0')
|
||||
assert.match(source, /export as namespace artplayerPluginVttThumbnail/)
|
||||
}
|
||||
})
|
||||
|
||||
test('VTT-thumbnail runnable CommonJS generations and script globals retain their actual shapes without import-time fetch', () => {
|
||||
for (const implementation of implementations.filter(item => !item.name.includes('source'))) {
|
||||
const env = vttThumbnailEnvironment(implementation, { text: acceptedVttText(implementation) })
|
||||
assert.equal(typeof env.factory, 'function')
|
||||
assert.equal(env.requests.length, 0)
|
||||
assert.equal(typeof env.exported, implementation.version.startsWith('1.0.') ? 'object' : 'function')
|
||||
if (typeof env.exported === 'object')
|
||||
assert.deepEqual(Object.keys(env.exported), ['default'])
|
||||
const script = vttThumbnailEnvironment(implementation, { script: true })
|
||||
assert.equal(typeof script.factory, 'function')
|
||||
assert.equal(script.requests.length, 0)
|
||||
}
|
||||
})
|
||||
|
||||
test('VTT-thumbnail actual latest ESM modules expose default-only factories and asynchronously register after fetch', async () => {
|
||||
const codes = [readMember(contract.archives.get('1.1.0'), `package/${contract.baseline.release.manifest.module.replace(/^\.\//, '')}`).toString(), contract.sources.get(`packages/${name}/dist/${name}.mjs`)]
|
||||
const original = globalThis.fetch
|
||||
try {
|
||||
for (const code of codes) {
|
||||
const requests = []
|
||||
globalThis.fetch = async (url) => {
|
||||
requests.push(url)
|
||||
return { text: async () => vttText }
|
||||
}
|
||||
const exported = await import(`data:text/javascript,${encodeURIComponent(code)}`)
|
||||
assert.deepEqual(Object.keys(exported), ['default'])
|
||||
const env = vttThumbnailEnvironment(implementations[0])
|
||||
const result = exported.default({ vtt: 'sprite.vtt' })(env.art)
|
||||
assert.equal(result.name, undefined)
|
||||
assert.equal(typeof result.then, 'function')
|
||||
assert.deepEqual(await result, { name: 'artplayerPluginVttThumbnail' })
|
||||
assert.deepEqual(requests, ['sprite.vtt'])
|
||||
}
|
||||
}
|
||||
finally { globalThis.fetch = original }
|
||||
})
|
||||
|
||||
test('VTT-thumbnail latest published members match their separately frozen workspace files after line-ending normalization', () => {
|
||||
for (const member of Object.keys(contract.baseline.release.files)) {
|
||||
const published = readMember(contract.archives.get('1.1.0'), member).toString().replaceAll('\r\n', '\n')
|
||||
assert.equal(published, contract.sources.get(`packages/${name}/${member.slice('package/'.length)}`))
|
||||
}
|
||||
})
|
||||
|
||||
test('VTT-thumbnail README and example select the VTT demo and its actual local sample assets', () => {
|
||||
for (const release of [contract.baseline.release, ...contract.baseline.previous]) {
|
||||
const readme = readMember(contract.archives.get(release.version), 'package/README.md').toString()
|
||||
assert.match(readme, /example=vtt\.thumbnail/)
|
||||
}
|
||||
const example = contract.sources.get('docs/assets/example/vtt.thumbnail.js')
|
||||
assert.match(example, /artplayerPluginVttThumbnail\(\{/)
|
||||
for (const file of ['bbb-video.mp4', 'bbb-thumbnails.vtt']) {
|
||||
assert(example.includes(`/assets/sample/${file}`))
|
||||
assert(fs.existsSync(`docs/assets/sample/${file}`))
|
||||
}
|
||||
})
|
||||
|
||||
for (const implementation of implementations) {
|
||||
test(`VTT-thumbnail ${implementation.name}: registration fetches once and adds controls only after awaiting text`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { deferred: true, text: acceptedVttText(implementation) })
|
||||
const option = { vtt: 'https://cdn.test/folder/cues.vtt', style: { opacity: '0.8' } }
|
||||
const register = env.factory(option)
|
||||
assert.equal(env.requests.length, 0)
|
||||
const result = register(env.art)
|
||||
assert.equal(typeof result.then, 'function')
|
||||
assert.equal(result.name, undefined)
|
||||
assert.deepEqual(env.requests, [[option.vtt]])
|
||||
assert.equal(env.controls.length, 0)
|
||||
assert.equal(env.listeners.size, 0)
|
||||
env.resolve()
|
||||
assert.deepEqual({ ...await result }, { name: 'artplayerPluginVttThumbnail' })
|
||||
assert.equal(env.controls.length, 1)
|
||||
assert.equal(env.controls[0].name, ['mouse-source-only', 'old-control'].includes(implementation.profile) ? 'thumbnails' : 'vtt-thumbnail')
|
||||
assert.equal(env.controls[0].position, 'top')
|
||||
assert.equal(env.controls[0].index, 20)
|
||||
assert.equal(env.controls[0].style, option.style)
|
||||
assert.equal(env.classes.has('art-control-thumbnails'), implementation.profile === 'current')
|
||||
})
|
||||
|
||||
test(`VTT-thumbnail ${implementation.name}: floored inclusive cue bounds select the first matching sprite and retain geometry`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { text: acceptedVttText(implementation) })
|
||||
await env.factory({ vtt: 'https://cdn.test/folder/cues.vtt' })(env.art)
|
||||
await env.hover(0.05)
|
||||
assert.equal(env.styles.left, 0)
|
||||
assert.equal(env.styles.backgroundImage, 'url(https://cdn.test/folder/sheet.jpg)')
|
||||
assert.equal(env.styles.backgroundPosition, '-10px -20px')
|
||||
assert.equal(env.styles.height, '45px')
|
||||
assert.equal(env.styles.width, '80px')
|
||||
await env.hover(0.5)
|
||||
assert.equal(env.styles.backgroundImage, 'url(https://cdn.test/folder/sheet.jpg)', 'At the shared floored boundary the earlier inclusive cue wins')
|
||||
assert.equal(env.styles.left, '60px')
|
||||
await env.hover(0.51)
|
||||
assert.equal(env.styles.backgroundImage, 'url(https://cdn.test/folder/second.jpg)')
|
||||
assert.equal(env.styles.backgroundPosition, '-90px -65px')
|
||||
await env.hover(0.99)
|
||||
assert.equal(env.styles.left, '120px')
|
||||
assert.equal(env.styles.display, implementation.profile === 'mouse-source-only' ? 'block' : 'flex')
|
||||
})
|
||||
|
||||
test(`VTT-thumbnail ${implementation.name}: style is read after fetch while the VTT URL is captured before it`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { deferred: true, text: acceptedVttText(implementation) })
|
||||
const options = { vtt: '/original.vtt', style: { opacity: '0.1' } }
|
||||
const result = env.factory(options)(env.art)
|
||||
options.vtt = '/later.vtt'
|
||||
options.style = { opacity: '0.9' }
|
||||
env.resolve()
|
||||
await result
|
||||
assert.deepEqual(env.requests, [['/original.vtt']])
|
||||
assert.equal(env.controls[0].style, options.style)
|
||||
})
|
||||
|
||||
test(`VTT-thumbnail ${implementation.name}: image URLs preserve literal joining and absolute forms`, async () => {
|
||||
for (const [image, expected] of [['../sprite.jpg', 'https://cdn.test/folder/../sprite.jpg'], ['/sprite.jpg', '/sprite.jpg'], ['https://img.test/s.jpg', 'https://img.test/s.jpg'], ['//img.test/s.jpg', '//img.test/s.jpg']]) {
|
||||
const env = vttThumbnailEnvironment(implementation, { text: acceptedVttText(implementation, `WEBVTT\n\n00:00:00.000 --> 00:00:10.000\n${image}#xywh=1,2,80,45\n`) })
|
||||
await env.factory({ vtt: 'https://cdn.test/folder/cues.vtt' })(env.art)
|
||||
await env.hover(0.5)
|
||||
assert.equal(env.styles.backgroundImage, `url(${expected})`)
|
||||
}
|
||||
})
|
||||
|
||||
test(`VTT-thumbnail ${implementation.name}: omitted option rejects asynchronously while omitted vtt fetches the empty string`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { text: acceptedVttText(implementation) })
|
||||
const result = env.factory()(env.art)
|
||||
assert.equal(typeof result.then, 'function')
|
||||
await assert.rejects(result, error => error.name === 'TypeError')
|
||||
assert.equal(env.requests.length, 0)
|
||||
await env.factory({})(env.art)
|
||||
assert.deepEqual(env.requests, [['']])
|
||||
})
|
||||
|
||||
if (implementation.profile !== 'mouse-source-only') {
|
||||
test(`VTT-thumbnail ${implementation.name}: setBar handles mobile played drags with a replaceable 500ms hide timer`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { mobile: true, text: acceptedVttText(implementation) })
|
||||
await env.factory({ vtt: '/cues.vtt' })(env.art)
|
||||
await env.hover(0.3, 'played', null)
|
||||
assert.deepEqual(env.styles, {})
|
||||
await env.hover(0.3, 'played', {})
|
||||
assert.equal(env.styles.display, 'flex')
|
||||
assert.equal(env.timers.size, 1)
|
||||
await env.hover(0.7, 'played', {})
|
||||
assert.equal(env.timers.size, 1)
|
||||
const timer = [...env.timers.values()][0]
|
||||
assert.equal(timer.delay, 500)
|
||||
timer.callback()
|
||||
assert.equal(env.styles.display, 'none')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test('VTT-thumbnail 1.0.0 source-only retains native mousemove/mouseleave and hover-hide integration', async () => {
|
||||
const implementation = implementations.find(item => item.profile === 'mouse-source-only')
|
||||
const env = vttThumbnailEnvironment(implementation, { text: acceptedVttText(implementation) })
|
||||
await env.factory({})(env.art)
|
||||
assert.deepEqual([...env.proxies.keys()], ['mousemove', 'mouseleave'])
|
||||
assert.deepEqual([...env.listeners.keys()], ['hover'])
|
||||
await env.hover(0)
|
||||
assert.equal(env.styles.display, 'block')
|
||||
await env.emit('hover', false)
|
||||
assert.equal(env.styles.display, 'none')
|
||||
await env.hover(1)
|
||||
assert.equal(env.styles.display, 'block')
|
||||
env.proxies.get('mouseleave')[0]()
|
||||
assert.equal(env.styles.display, 'none')
|
||||
})
|
||||
|
||||
for (const implementation of implementations) {
|
||||
test(`VTT-thumbnail ${implementation.name}: standard spaced arrows remain distinct from historical compact-only bundles`, async () => {
|
||||
const env = vttThumbnailEnvironment(implementation, { text: vttText })
|
||||
const result = env.factory({ vtt: '/cues.vtt' })(env.art)
|
||||
if (/^published-1\.0\.[123]-(?:main|legacy)$/.test(implementation.name)) {
|
||||
await assert.rejects(result, error => error.name === 'TypeError')
|
||||
assert.equal(env.controls.length, 0)
|
||||
}
|
||||
else {
|
||||
assert.equal((await result).name, 'artplayerPluginVttThumbnail')
|
||||
await env.hover(0.3)
|
||||
assert.equal(env.styles.backgroundImage, 'url(/sheet.jpg)')
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user