test(multiple-subtitles): [PKG-MULTI-SUB-01] freeze contracts and verify parser provenance

This commit is contained in:
Harvey Zhao committed 2026-09-13 10:12:32 +08:00
1 parent 0c5bb2bd7a
commit 5a0d94cb89
33 files changed
+2375 -22

No files matched your search

+2 -1
View File
@@ -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, 25)
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 28)
assert.deepEqual(actual.policy.versions.filter(version => version.kind === 'published-baseline' && version.package === 'artplayer-plugin-multiple-subtitles').map(version => version.version).sort(), ['1.0.0', '1.1.0', '1.2.0'])
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'])
@@ -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 verifyMultipleSubtitlesContract() {
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/multiple-subtitles-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)
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, `Multiple-subtitles frozen source: ${file}`)
sources.set(file, source)
}
return { baseline, archives, sources }
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const { baseline } = await verifyMultipleSubtitlesContract()
console.log(`Multiple-subtitles verified: 3 actual npm archives, ${[baseline.release, ...baseline.previous].reduce((sum, item) => sum + Object.keys(item.files).length, 0)} members, ${Object.keys(baseline.source).length} frozen inputs`)
}
@@ -0,0 +1,93 @@
import assert from 'node:assert/strict'
import { Buffer } from 'node:buffer'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Published package contract runner.
import test from 'node:test'
import { transformSync } from 'esbuild'
import { multipleSubtitlesEnvironment, multipleSubtitlesHistorical } from '../../test/helpers/multiple-subtitles.js'
import { verifyMultipleSubtitlesContract } from './multiple-subtitles-contract.mjs'
import { adaptMultipleSubtitlesParser, checkMultipleSubtitlesVendor } from './multiple-subtitles-vendor.mjs'
import { hash, readMember } from './releases.mjs'
const contract = await verifyMultipleSubtitlesContract()
const implementations = await multipleSubtitlesHistorical()
const name = 'artplayer-plugin-multiple-subtitles'
test('Multiple-subtitles freezes three actual npm archives and Git inputs without inventing core support', () => {
assert.deepEqual([contract.baseline.release, ...contract.baseline.previous].map(item => item.version), ['1.2.0', '1.1.0', '1.0.0'])
assert.equal([...contract.archives].length, 3)
assert.equal([contract.baseline.release, ...contract.baseline.previous].reduce((sum, item) => sum + Object.keys(item.files).length, 0), 20)
assert.equal(contract.sources.size, 9)
assert.deepEqual([contract.baseline.release, ...contract.baseline.previous].map(item => item.historicalCore.version), ['5.3.1', '5.1.7', '5.1.2'])
})
test('Multiple-subtitles published declarations keep required subtitles while omitting Promise and real methods', () => {
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, /subtitles: \{/)
for (const field of ['url', 'name', 'encoding'])
assert.match(source, new RegExp(`${field}\\?: string`))
assert.match(source, /type\?: 'vtt' \| 'srt' \| 'ass'/)
assert.match(source, /onParser\?(?::|\()/)
assert.match(source, /name: 'multipleSubtitles'/)
assert.doesNotMatch(source, /Promise<|tracks\(|reset\(/)
assert.match(source, release.version === '1.2.0' ? /export default artplayerPluginMultipleSubtitles/ : /export = artplayerPluginMultipleSubtitles/)
}
})
test('Multiple-subtitles historical CommonJS and globals load without fetching and preserve actual export shapes', () => {
for (const implementation of implementations.filter(item => !item.name.includes('source'))) {
const env = multipleSubtitlesEnvironment(implementation)
assert.equal(typeof env.factory, 'function')
assert.equal(typeof env.exported, ['1.0.0', '1.1.0'].includes(implementation.version) ? 'object' : 'function')
if (typeof env.exported === 'object')
assert.deepEqual(Object.keys(env.exported), ['default'])
const browser = multipleSubtitlesEnvironment(implementation, { script: true })
assert.equal(typeof browser.factory, 'function')
assert.deepEqual(env.requests, [])
assert.deepEqual(browser.requests, [])
}
})
test('Multiple-subtitles latest native ESM exports only the factory and asynchronously registers named methods', async () => {
const codes = [readMember(contract.archives.get('1.2.0'), 'package/dist/artplayer-plugin-multiple-subtitles.mjs').toString(), contract.sources.get(`packages/${name}/dist/${name}.mjs`)]
for (const code of codes) {
const exported = await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`)
assert.deepEqual(Object.keys(exported), ['default'])
const env = multipleSubtitlesEnvironment(implementations[0])
try {
const pending = exported.default({ subtitles: [] })(env.art)
assert.equal(pending.name, undefined)
const result = await pending
assert.deepEqual(Object.keys(result), ['name', 'tracks', 'reset'])
assert.equal(result.tracks([]), undefined)
}
finally {
for (const option of env.initialized)
URL.revokeObjectURL(option.url)
}
}
})
test('Multiple-subtitles shipped source parsers match the pinned upstream adaptation', () => {
const normalized = checkMultipleSubtitlesVendor().normalizedSha256
for (const release of contract.baseline.previous) {
const source = readMember(contract.archives.get(release.version), 'package/src/parser.js').toString()
assert(source.startsWith('// Any copyright is dedicated to the Public Domain.'))
assert.equal(hash(transformSync(adaptMultipleSubtitlesParser(source), { format: 'esm', target: 'es2020', minifyWhitespace: true, legalComments: 'none' }).code), normalized)
}
})
test('Multiple-subtitles README and demo retain name-based switching, reset and per-language CSS hooks', () => {
for (const release of [contract.baseline.release, ...contract.baseline.previous])
assert.match(readMember(contract.archives.get(release.version), 'package/README.md').toString(), /example=multiple\.subtitles/)
const source = contract.sources.get('docs/assets/example/multiple.subtitles.js')
assert(source.includes('art.plugins.multipleSubtitles.tracks('))
assert(source.includes('art.plugins.multipleSubtitles.reset()'))
for (const language of ['chinese', 'japanese'])
assert(source.includes(`.art-subtitle-${language}`))
for (const file of ['video.mp4', 'subtitle.cn.srt', 'subtitle.jp.srt']) {
assert(source.includes(`/assets/sample/${file}`))
assert(fs.existsSync(`docs/assets/sample/${file}`))
}
})
@@ -0,0 +1,35 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import { transformSync } from 'esbuild'
import { hash } from './releases.mjs'
const base = 'refactor/baselines/multiple-subtitles-vendor'
export const vendorExports = 'export { WebVTTParser, WebVTTCueTimingsAndSettingsParser, WebVTTCueTextParser, WebVTTSerializer };'
export function adaptMultipleSubtitlesParser(upstream) {
const start = '(function () {'
const end = ' function exportify(object)'
assert.equal(upstream.split(start).length, 2)
assert.equal(upstream.split(end).length, 2)
assert(upstream.slice(0, upstream.indexOf(start)).includes('http://creativecommons.org/publicdomain/zero/1.0/'))
const footer = upstream.slice(upstream.indexOf(end)).replaceAll(/[\s;]/g, '')
assert.equal(footer, 'functionexportify(object){object.WebVTTParser=WebVTTParserobject.WebVTTCueTimingsAndSettingsParser=WebVTTCueTimingsAndSettingsParserobject.WebVTTCueTextParser=WebVTTCueTextParserobject.WebVTTSerializer=WebVTTSerializer}if(typeofwindow!==\'undefined\')exportify(window)if(typeofexports!==\'undefined\')exportify(exports)})()')
return upstream.slice(upstream.indexOf(start) + start.length, upstream.indexOf(end)) + vendorExports
}
export function checkMultipleSubtitlesVendor() {
const provenance = JSON.parse(fs.readFileSync(`${base}/sources.json`, 'utf8'))
for (const source of provenance.sources)
assert.equal(hash(fs.readFileSync(source.file)), source.sha256, source.file)
const upstream = fs.readFileSync(`${base}/parser.txt`, 'utf8')
const local = fs.readFileSync('packages/artplayer-plugin-multiple-subtitles/src/parser.js', 'utf8')
assert(local.startsWith('// Any copyright is dedicated to the Public Domain.'))
const normalize = source => transformSync(source, { format: 'esm', target: 'es2020', minifyWhitespace: true, legalComments: 'none' }).code
const expected = normalize(adaptMultipleSubtitlesParser(upstream))
assert.equal(normalize(local), expected, 'Unreviewed WebVTT parser adaptation')
const notices = fs.readFileSync('packages/artplayer-plugin-multiple-subtitles/THIRD_PARTY_NOTICES', 'utf8').replaceAll('\r\n', '\n')
const license = fs.readFileSync(`${base}/license.txt`, 'utf8').replaceAll('\r\n', '\n').trim()
assert(notices.includes(license), 'Complete upstream dedication must be distributed')
assert(notices.includes(provenance.revision))
return { provenance, normalizedSha256: hash(expected), localSha256LF: hash(local.replaceAll('\r\n', '\n')), noticesSha256LF: hash(notices) }
}