mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-10 12:46:15 -08:00
test(auto-thumbnail): [PKG-AUTO-THUMB-01] freeze published and workspace contracts
This commit is contained in:
1 parent
17befee4b1
commit
cdc252b158
15 files changed
+919
-8
No files matched your search
@@ -0,0 +1,38 @@
|
||||
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 verifyAutoThumbnailContract() {
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/auto-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, 'Missing entries are historical defects, not runnable artifacts')
|
||||
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, `Auto-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 verifyAutoThumbnailContract()
|
||||
console.log('Auto-thumbnail verified: 3 actual npm archives, 16 members, 8 frozen workspace inputs; 1.0.0 missing main/legacy explicitly retained')
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Node frozen contract runner.
|
||||
import test from 'node:test'
|
||||
import { transform } from 'esbuild'
|
||||
import { autoThumbnailEnvironment, autoThumbnailHistorical } from '../../test/helpers/auto-thumbnail.js'
|
||||
import { verifyAutoThumbnailContract } from './auto-thumbnail-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const contract = await verifyAutoThumbnailContract()
|
||||
const implementations = await autoThumbnailHistorical()
|
||||
const name = 'artplayer-plugin-auto-thumbnail'
|
||||
|
||||
test('Auto-thumbnail freezes all three actual archives, sixteen members and eight workspace inputs', () => {
|
||||
const { baseline } = contract
|
||||
const releases = [baseline.release, ...baseline.previous]
|
||||
assert.deepEqual(releases.map(item => item.version), ['1.1.0', '1.0.1', '1.0.0'])
|
||||
assert.equal(releases.reduce((sum, item) => sum + Object.keys(item.files).length, 0), 16)
|
||||
assert.equal(Object.keys(baseline.source).length, 8)
|
||||
assert.deepEqual(releases.map(item => item.historicalCore.version), ['5.3.1', '5.2.2', '5.2.2'])
|
||||
for (const release of releases) {
|
||||
assert.equal(release.manifest.peerDependencies, undefined)
|
||||
assert.equal(release.manifest.dependencies, undefined)
|
||||
}
|
||||
})
|
||||
|
||||
test('Auto-thumbnail 1.0.0 lacks both declared runtimes; its shipped source is explicitly a source-only fixture', async () => {
|
||||
const { baseline, archives } = contract
|
||||
const release = baseline.previous.find(item => item.version === '1.0.0')
|
||||
assert.deepEqual(release.missingEntrypoints, { main: `dist/${name}.js`, legacy: `dist/${name}.legacy.js` })
|
||||
assert(!implementations.some(item => item.name === 'published-1.0.0-main' || item.name === 'published-1.0.0-legacy'))
|
||||
assert(implementations.some(item => item.name === 'published-1.0.0-source-only'))
|
||||
const sources = ['1.0.0', '1.0.1'].map(version => readMember(archives.get(version), 'package/src/index.js').toString())
|
||||
const compiled = await Promise.all(sources.map(source => transform(source, { format: 'esm', target: 'es2020', minify: true })))
|
||||
assert.equal(compiled[0].code, compiled[1].code, 'The two shipped source implementations differ only in formatting')
|
||||
})
|
||||
|
||||
test('Auto-thumbnail historical declarations require options and falsely describe async registration as synchronous', () => {
|
||||
const { baseline, archives, sources } = contract
|
||||
const declarations = [baseline.release, ...baseline.previous].map(release => ({ version: release.version, source: readMember(archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`).toString() }))
|
||||
declarations.push({ version: 'workspace', source: sources.get(`packages/${name}/types/${name}.d.ts`) })
|
||||
for (const { version, source } of declarations) {
|
||||
assert.match(source, /\(option: Option\) => \(art: Artplayer\) => Result/)
|
||||
assert.match(source, /url\?: string/)
|
||||
for (const field of ['width', 'scale']) assert.match(source, new RegExp(`${field}\\?: number`))
|
||||
if (version === '1.0.0' || version === '1.0.1') {
|
||||
assert.match(source, /height\?: number/)
|
||||
assert.doesNotMatch(source, /number\?: number/)
|
||||
assert.match(source, /export = artplayerPluginAutoThumbnail/)
|
||||
assert.match(source, /export as namespace artplayerPluginAutoThumbnail/)
|
||||
}
|
||||
else {
|
||||
assert.match(source, /number\?: number/)
|
||||
assert.doesNotMatch(source, /height\?: number/)
|
||||
assert.match(source, /export default artplayerPluginAutoThumbnail/)
|
||||
}
|
||||
assert.match(source, /name: ['"]artplayerPluginAutoThumbnail['"]/)
|
||||
assert.doesNotMatch(source, /Promise</)
|
||||
}
|
||||
})
|
||||
|
||||
test('Auto-thumbnail actual CommonJS generations and script globals remain distinct', () => {
|
||||
for (const implementation of implementations.filter(item => !item.name.includes('source'))) {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
assert.equal(typeof env.factory, 'function')
|
||||
assert.equal(typeof env.exported, implementation.name.startsWith('published-1.0.1-') ? 'object' : 'function')
|
||||
if (typeof env.exported === 'object')
|
||||
assert.deepEqual(Object.keys(env.exported), ['default'])
|
||||
else assert.equal(env.exported.default, undefined)
|
||||
const global = autoThumbnailEnvironment(implementation, { script: true })
|
||||
assert.equal(typeof global.factory, 'function')
|
||||
assert.equal(global.videos.length, 0, 'Import/global setup itself creates no decoder')
|
||||
}
|
||||
})
|
||||
|
||||
test('Auto-thumbnail native ESM has only a default function and preserves asynchronous registration', async () => {
|
||||
const { baseline, archives, sources } = contract
|
||||
const codes = [readMember(archives.get('1.1.0'), `package/${baseline.release.manifest.module.replace(/^\.\//, '')}`).toString(), sources.get(`packages/${name}/dist/${name}.mjs`)]
|
||||
for (const code of codes) {
|
||||
const exports = await import(`data:text/javascript,${encodeURIComponent(code)}`)
|
||||
assert.deepEqual(Object.keys(exports), ['default'])
|
||||
let registration
|
||||
const result = exports.default({})({ on: (...args) => {
|
||||
registration = args
|
||||
} })
|
||||
assert.equal(registration[0], 'video:loadedmetadata')
|
||||
assert.equal(typeof result.then, 'function')
|
||||
assert.deepEqual(await result, { name: 'artplayerPluginAutoThumbnail' })
|
||||
}
|
||||
})
|
||||
|
||||
test('Auto-thumbnail README selects its own runnable demo; workspace demo and declarations are frozen separately from external thumbnail plugin', () => {
|
||||
const { baseline, archives, sources } = contract
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
const readme = readMember(archives.get(release.version), 'package/README.md').toString()
|
||||
assert.match(readme, /example=auto\.thumbnail/)
|
||||
}
|
||||
assert.match(sources.get(`packages/${name}/README.md`), /example=auto\.thumbnail/)
|
||||
const example = sources.get('docs/assets/example/auto.thumbnail.js')
|
||||
assert.match(example, /artplayerPluginAutoThumbnail\(\{/)
|
||||
assert.match(example, /\/assets\/sample\/video\.mp4/)
|
||||
assert(fs.existsSync('docs/assets/sample/video.mp4'))
|
||||
})
|
||||
|
||||
for (const implementation of implementations) {
|
||||
test(`Auto-thumbnail ${implementation.name}: registration installs one listener synchronously and returns Promise of name only`, async () => {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
const register = env.factory({})
|
||||
assert.equal(typeof register, 'function')
|
||||
assert.equal(env.listeners.size, 0)
|
||||
const result = register(env.art)
|
||||
assert.equal(env.listeners.get('video:loadedmetadata').size, 1)
|
||||
assert.equal(typeof result.then, 'function')
|
||||
assert.equal(result.name, undefined)
|
||||
assert.deepEqual({ ...await result }, { name: 'artplayerPluginAutoThumbnail' })
|
||||
assert.equal(env.videos.length, 0)
|
||||
assert.deepEqual([...env.listeners.keys()], ['video:loadedmetadata'])
|
||||
})
|
||||
|
||||
test(`Auto-thumbnail ${implementation.name}: defaults use truthiness and options remain live until each metadata event`, async () => {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
const options = { url: '', width: 0, number: 0, scale: 0 }
|
||||
await env.factory(options)(env.art)
|
||||
env.art.emit('video:loadedmetadata')
|
||||
assert.equal(env.videos[0].src, 'original.mp4')
|
||||
assert.equal(env.videos[0].crossOrigin, 'anonymous')
|
||||
env.metadata()
|
||||
assert.equal(env.canvases[0].width, 1600)
|
||||
assert.equal(env.canvases[0].height, 900)
|
||||
env.finish()
|
||||
assert.deepEqual({ ...env.updates[0] }, { url: 'blob:auto-thumbnail-1', height: 90, column: 10, number: 100, width: 160, scale: 1 })
|
||||
Object.assign(options, { url: 'override.mp4', width: 80, number: 12, scale: 0.5 })
|
||||
env.art.option.url = 'switched.mp4'
|
||||
env.art.emit('video:loadedmetadata')
|
||||
assert.equal(env.videos[1].src, 'override.mp4')
|
||||
env.metadata()
|
||||
assert.equal(env.canvases[1].width, 800)
|
||||
assert.equal(env.canvases[1].height, 90)
|
||||
env.finish(1)
|
||||
assert.deepEqual({ ...env.updates[1] }, { url: 'blob:auto-thumbnail-2', height: 45, column: 10, number: 12, width: 80, scale: 0.5 })
|
||||
})
|
||||
|
||||
test(`Auto-thumbnail ${implementation.name}: ten-column JPEG grid uses frame starts and progressive encodes including initial blank`, async () => {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
await env.factory({ width: 80, number: 12, scale: 2, height: 999 })(env.art)
|
||||
env.art.emit('video:loadedmetadata')
|
||||
env.metadata()
|
||||
assert.equal(env.blobs.length, 1)
|
||||
assert.equal(env.blobs[0].draws.length, 0, 'Old implementation encodes an empty sheet before drawing')
|
||||
for (let i = 0; i < 12; i++) env.seeked()
|
||||
assert.deepEqual(env.operations.filter(item => item.name === 'seek').map(item => item.value), Array.from({ length: 12 }, (_, i) => i * 10))
|
||||
assert.equal(env.operations.find(item => item.name === 'seek').handlerInstalled, false, 'First seek occurs before onseeked installation')
|
||||
assert.equal(env.blobs.length, 13)
|
||||
assert.equal(env.blobs.at(-1).draws.length, 12)
|
||||
assert.deepEqual(env.canvases[0].draws.map(args => args.slice(1)), Array.from({ length: 12 }, (_, i) => [(i % 10) * 80, Math.floor(i / 10) * 45, 80, 45]))
|
||||
for (let i = 0; i < env.blobs.length; i++) {
|
||||
assert.equal(env.blobs[i].type, 'image/jpeg')
|
||||
env.finish(i)
|
||||
}
|
||||
assert.equal(env.updates.length, 13)
|
||||
assert.deepEqual(env.operations.filter(item => item.name === 'revokeURL').map(item => item.url), [null, ...Array.from({ length: 12 }, (_, i) => `blob:auto-thumbnail-${i + 1}`)])
|
||||
assert.deepEqual([...env.urls.keys()], ['blob:auto-thumbnail-13'])
|
||||
assert.equal(env.updates.at(-1).number, 12)
|
||||
assert.equal(env.updates.at(-1).scale, 2)
|
||||
})
|
||||
|
||||
test(`Auto-thumbnail ${implementation.name}: omitted options fail only when metadata fires and no destroyed cleanup is registered`, async () => {
|
||||
const env = autoThumbnailEnvironment(implementation)
|
||||
const result = await env.factory()(env.art)
|
||||
assert.equal(result.name, 'artplayerPluginAutoThumbnail')
|
||||
assert.throws(() => env.art.emit('video:loadedmetadata'), /Cannot read properties of undefined/)
|
||||
assert.equal(env.videos.length, 0)
|
||||
assert.equal(env.listeners.has('destroy'), false)
|
||||
assert.equal(env.listeners.has('restart'), false)
|
||||
})
|
||||
}
|
||||
@@ -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, 17)
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 20)
|
||||
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)
|
||||
assert.equal(actual.policy.cases.length, 10)
|
||||
|
||||
Reference in new issue
Block a user