mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 01:56:16 -08:00
refactor(docs): [SITE-02] generate and validate standalone editor types
This commit is contained in:
1 parent
1a5440a67f
commit
76499e7a3e
30 files changed
+2955
-459
No files matched your search
@@ -1,5 +1,10 @@
|
||||
# Tests and fixture ownership
|
||||
|
||||
Editor tooling lives in `scripts/editor-declarations/`; see its README.
|
||||
`test/editor-types.test.js` checks standalone globals and module consumers with
|
||||
current/old compilers, Chapter/VAST regressions, generated files and library-list
|
||||
failures. Package baseline tests continue to exercise the old MJS import paths.
|
||||
|
||||
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
|
||||
|
||||
`yarn build:test` generates deterministic documentation readiness smoke and its
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Browser regression entry
|
||||
|
||||
`editor-declarations.spec.js` loads the repository Monaco with the 22 declarations
|
||||
actually listed in `common.js`, checks positive/negative consumers and runs emitted
|
||||
Chapter code through ready/destroy with controlled media. VAST coverage is types
|
||||
only, not advertising SDK execution. The unreferenced legacy WebSR declaration
|
||||
asset is preserved; this test follows the actual editor's library list.
|
||||
|
||||
`docs-smoke.spec.js` verifies the documentation runner in three engines with
|
||||
actual core/media, controlled failures and three selected generated snippets.
|
||||
It checks pending readiness beyond the former 100ms timer, returned/unhandled
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
test('Monaco checks all editor declarations together and runs the Chapter consumer', async ({ page }, testInfo) => {
|
||||
const common = fs.readFileSync('docs/assets/js/common.js', 'utf8')
|
||||
const list = common.match(/let libUris = \[([\s\S]*?)\]/)?.[1] || ''
|
||||
const names = [...list.matchAll(/'\.\/assets\/ts\/([^']+\.d\.ts)'/g)].map(match => match[1])
|
||||
expect(names).toHaveLength(22)
|
||||
await testInfo.attach('editor-declaration-inputs', { contentType: 'application/json', body: JSON.stringify(Object.fromEntries(names.map(name => [name, hash(fs.readFileSync(`docs/assets/ts/${name}`))]))) })
|
||||
await page.goto('/test/player.html?core=candidate&chapter=candidate')
|
||||
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
|
||||
const result = await page.evaluate(async (names) => {
|
||||
window.require.config({ paths: { vs: '/assets/js/vs' } })
|
||||
await new Promise((resolve, reject) => window.require(['vs/editor/editor.main'], resolve, reject))
|
||||
const api = window.monaco
|
||||
const types = api.languages.typescript
|
||||
types.typescriptDefaults.setCompilerOptions({ target: types.ScriptTarget.ES2020, strict: true, skipLibCheck: false, noEmit: false, types: [], allowNonTsExtensions: true })
|
||||
const libraries = await Promise.all(names.map(async (name) => {
|
||||
const source = await (await fetch(`/assets/ts/${name}`)).text()
|
||||
const uri = `file:///${name}`
|
||||
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
|
||||
}))
|
||||
const model = api.editor.createModel(`
|
||||
const chapters: artplayerPluginChapter.Chapters = [{ start: 0, end: 2, title: 'Intro' }];
|
||||
const player = new Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, plugins: [artplayerPluginChapter({ chapters })] });
|
||||
const chapter = player.plugins.artplayerPluginChapter as artplayerPluginChapter.Result;
|
||||
chapter.update({ chapters: [{ start: 0, end: 1, title: 'Updated' }] });
|
||||
const adOption: artplayerPluginVast.ArtplayerPluginVastOption = context => {
|
||||
const volume: number = context.init().volume;
|
||||
context.playerOptions.autoResize = true;
|
||||
return Promise.resolve();
|
||||
};
|
||||
`, 'typescript', api.Uri.parse('file:///editor-consumer.ts'))
|
||||
const invalid = api.editor.createModel(`
|
||||
artplayerPluginVast(context => { context.init().volume = 'loud'; context.playerOptions.autoResize = 'yes'; });
|
||||
artplayerPluginChapter({ chapters: [{ start: '0', end: 2, title: '' }] });
|
||||
`, 'typescript', api.Uri.parse('file:///editor-invalid.ts'))
|
||||
try {
|
||||
const worker = await (await types.getTypeScriptWorker())(model.uri, invalid.uri)
|
||||
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
|
||||
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
|
||||
const declarations = (await Promise.all(libraries.map(lib => worker.getSemanticDiagnostics(lib.uri)))).flat()
|
||||
const bad = (await worker.getSemanticDiagnostics(invalid.uri.toString())).map(item => item.code)
|
||||
if (syntax.length || semantic.length || declarations.length)
|
||||
return { syntax, semantic, declarations, bad }
|
||||
const output = await worker.getEmitOutput(model.uri.toString())
|
||||
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
|
||||
if (!script)
|
||||
throw new Error('Monaco did not emit the consumer')
|
||||
// eslint-disable-next-line no-new-func -- Execute the actual editor output with local controlled media.
|
||||
const player = new Function(`${script.text}\nreturn player`)()
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
player.on('ready', resolve)
|
||||
player.on('error', reject)
|
||||
})
|
||||
const name = player.plugins.artplayerPluginChapter.name
|
||||
const ready = player.isReady
|
||||
player.destroy(true)
|
||||
return { syntax, semantic, declarations, bad, name, ready, instances: window.Artplayer.instances.length }
|
||||
}
|
||||
finally {
|
||||
if (!player.isDestroy)
|
||||
player.destroy(true)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
model.dispose()
|
||||
invalid.dispose()
|
||||
for (const library of libraries) library.handle.dispose()
|
||||
}
|
||||
}, names)
|
||||
await testInfo.attach('editor-declaration-result', { contentType: 'application/json', body: JSON.stringify(result) })
|
||||
expect(result.syntax).toEqual([])
|
||||
expect(result.semantic).toEqual([])
|
||||
expect(result.declarations).toEqual([])
|
||||
expect(result.bad).toEqual([2322, 2322, 2322])
|
||||
expect(result.name).toBe('artplayerPluginChapter')
|
||||
expect(result.ready).toBe(true)
|
||||
expect(result.instances).toBe(0)
|
||||
})
|
||||
@@ -4,6 +4,9 @@ import fs from 'node:fs'
|
||||
import { test } from 'node:test'
|
||||
import { ESLint } from 'eslint'
|
||||
import compat from 'typescript-compat'
|
||||
import { vastSdkDeclarations } from '../scripts/editor-declarations/dependencies.ts'
|
||||
import { editorLibUris, generateEditorDeclarations } from '../scripts/editor-declarations/generate.ts'
|
||||
import { checkStandaloneDeclarations } from '../scripts/editor-declarations/validation.ts'
|
||||
import { asGlobalDeclaration, checkCoreEditorDeclaration, generateCoreEditorDeclaration } from '../scripts/editor-types.mjs'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../scripts/plugin-editor-types.mjs'
|
||||
|
||||
@@ -21,6 +24,74 @@ test('editor declarations are reproducible, standalone and preserve constructor/
|
||||
assert.throws(() => asGlobalDeclaration('export { default } from "./missing"', 'Artplayer'), /unresolved exports/)
|
||||
})
|
||||
|
||||
test('Chapter and VAST editor consumers preserve SDK types, optional Window hook and named results', () => {
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const generated = new Map([['artplayer.d.ts', core]])
|
||||
for (const [suffix, global] of [['chapter', 'artplayerPluginChapter'], ['vast', 'artplayerPluginVast']]) {
|
||||
const source = fs.readFileSync(`packages/artplayer-plugin-${suffix}/types/artplayer-plugin-${suffix}.d.ts`, 'utf8')
|
||||
const old = `${source.replace(/^import.*$/gim, '')}\nexport = ${global};\nexport as namespace ${global};\n`
|
||||
const diagnostics = checkPluginEditorDeclaration(old, core)
|
||||
assert(diagnostics.some(item => item.code === 2309))
|
||||
if (suffix === 'vast')
|
||||
assert(diagnostics.some(item => item.code === 2304 && item.message.includes('Player')))
|
||||
generated.set(`${suffix}.d.ts`, generatePluginEditorDeclaration(source, global, {}, suffix === 'vast' ? vastSdkDeclarations() : []))
|
||||
}
|
||||
const consumer = `
|
||||
import chapter = require('./chapter');
|
||||
import vast = require('./vast');
|
||||
declare const art: Artplayer;
|
||||
const chapters: chapter.Chapters = [{ start: 0, end: 10, title: 'Intro' }];
|
||||
const result: chapter.Result = chapter({ chapters })(art);
|
||||
result.update({ chapters: [] });
|
||||
const option: vast.ArtplayerPluginVastOption = async context => {
|
||||
const player = context.init();
|
||||
const volume: number = player.volume;
|
||||
player.volume = 0.5;
|
||||
player.pause();
|
||||
player.play();
|
||||
context.playUrl('ad.xml', { oldUntypedConfig: true });
|
||||
// @ts-expect-error SDK numeric property remains checked.
|
||||
player.volume = 'loud';
|
||||
// @ts-expect-error SDK methods must not degrade to any.
|
||||
player.missingMethod();
|
||||
// @ts-expect-error Private SDK implementation stays private.
|
||||
player._setupIma();
|
||||
void volume;
|
||||
};
|
||||
const instance: vast.ArtplayerPluginVastInstance = vast(option)(art);
|
||||
const windowFactory: typeof vast | undefined = window.artplayerPluginVast;
|
||||
// @ts-expect-error Historical required callback is still required.
|
||||
vast();
|
||||
// @ts-expect-error Chapter timestamps stay numeric.
|
||||
chapter({ chapters: [{ start: '0', end: 1, title: '' }] });
|
||||
// @ts-expect-error SDK internals are private to the declaration module.
|
||||
declare const leaked: artplayerPluginVastDefinitions.Player;
|
||||
void [result, instance, windowFactory, leaked];
|
||||
`
|
||||
generated.set('global.ts', 'declare const globalArt: Artplayer; const globalResult: artplayerPluginChapter.Result = artplayerPluginChapter()(globalArt);')
|
||||
for (const compiler of [undefined, compat]) {
|
||||
generated.set('consumer.ts', consumer)
|
||||
assert.deepEqual(checkStandaloneDeclarations(generated, compiler), [])
|
||||
generated.set('consumer.ts', consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
|
||||
const errors = checkStandaloneDeclarations(generated, compiler)
|
||||
assert.equal(errors.length, 6, 'Every intentional misuse must remain rejected')
|
||||
}
|
||||
})
|
||||
|
||||
test('all editor outputs are generated and checked together without modifying source declarations', async () => {
|
||||
const outputs = await generateEditorDeclarations()
|
||||
assert.equal([...outputs.keys()].filter(file => file.endsWith('.d.ts')).length, 22)
|
||||
for (const [file, code] of outputs)
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), code.replaceAll('\r\n', '\n'), `Stale editor output: ${file}`)
|
||||
await assert.rejects(generateEditorDeclarations(['missing-plugin']), /Unknown declaration package/)
|
||||
await assert.rejects(generateEditorDeclarations(['artplayer-plugin-chapter', 'artplayer-plugin-chapter']), /Duplicate/)
|
||||
const source = 'const note = \'libUris\'; let libUris = [\'old\']; const untouched = 1'
|
||||
assert.equal(editorLibUris(source, ['chapter.d.ts']), 'const note = \'libUris\'; let libUris = [\n \'./assets/ts/chapter.d.ts\',\n ]; const untouched = 1')
|
||||
assert.throws(() => editorLibUris('const unrelated = []', []), /exactly one/)
|
||||
assert.throws(() => editorLibUris('let libUris = []; function x() { let libUris = [] }', []), /exactly one/)
|
||||
assert.throws(() => editorLibUris('let libUris = fetch()', []), /array initializer/)
|
||||
})
|
||||
|
||||
test('ASR editor generation supports named types without mixing default and export assignment', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts', 'utf8')
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
|
||||
Reference in new issue
Block a user