refactor(vast): [PKG-VAST-04] retain published types and add accurate runtime declarations

This commit is contained in:
Harvey Zhao committed 2026-09-14 23:07:52 +08:00
1 parent c162d2b3b3
commit e5c73fa620
34 files changed
+7819 -1495

No files matched your search

+22 -4
View File
@@ -1,6 +1,6 @@
import { expect, test } from './fixtures.js'
test('docs Monaco worker consumes generated core types and runs a typed player example', async ({ page }) => {
test('docs Monaco worker consumes generated core and published VAST types and runs a typed player example', async ({ page }, testInfo) => {
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
const result = await page.evaluate(async () => {
@@ -12,23 +12,36 @@ test('docs Monaco worker consumes generated core types and runs a typed player e
const source = await (await fetch('/assets/ts/artplayer.d.ts')).text()
const libraryUri = 'file:///artplayer.d.ts'
const library = types.typescriptDefaults.addExtraLib(source, libraryUri)
const vastSource = await (await fetch('/assets/ts/artplayer-plugin-vast.d.ts')).text()
const vastLibraryUri = 'file:///artplayer-plugin-vast.d.ts'
const vastLibrary = types.typescriptDefaults.addExtraLib(vastSource, vastLibraryUri)
const model = api.editor.createModel(`
const editorOption: Artplayer.OptionInput = { container: '.player', url: '/test/pattern.mp4', muted: true }
const editorArt: Artplayer = new Artplayer(editorOption)
const editorEvents: Artplayer.Emitter<{ value: [number] }> = new Artplayer.Emitter()
editorEvents.emit('value', 1)
const editorVastCallback: Parameters<typeof artplayerPluginVast>[0] = ({id, $container, imaPlayer}) => {
const identifier: string = id
const container: HTMLDivElement = $container
imaPlayer.addEventListener('AdStarted', () => {})
void [identifier, container]
}
const editorVastFactory: typeof artplayerPluginVast = (_callback) => (_art) => ({name: 'artplayerPluginVast'})
`, 'typescript', api.Uri.parse('file:///consumer.ts'))
const invalid = api.editor.createModel('new Artplayer({ container: 123, url: "" })', 'typescript', api.Uri.parse('file:///invalid.ts'))
const vastInvalid = api.editor.createModel('artplayerPluginVast();\nartplayerPluginVast(({id}) => { const wrong: number = id })', 'typescript', api.Uri.parse('file:///vast-invalid.ts'))
try {
const getWorker = await types.getTypeScriptWorker()
const worker = await getWorker(model.uri, invalid.uri)
const worker = await getWorker(model.uri, invalid.uri, vastInvalid.uri)
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
const declaration = await worker.getSemanticDiagnostics(libraryUri)
const vastDeclaration = await worker.getSemanticDiagnostics(vastLibraryUri)
const vastBad = (await worker.getSemanticDiagnostics(vastInvalid.uri.toString())).map(item => item.code)
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
const output = await worker.getEmitOutput(model.uri.toString())
if (syntax.length || semantic.length || declaration.length)
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), ready: false }
return { syntax, semantic, declaration, vastDeclaration, vastBad, vastSource, invalid: bad.map(item => item.code), ready: false }
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
if (!script)
throw new Error('Monaco did not emit the typed example')
@@ -37,7 +50,7 @@ editorEvents.emit('value', 1)
try {
if (!art.isReady)
await new Promise(resolve => art.on('ready', resolve))
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), ready: art.isReady && art.video.videoWidth > 0 }
return { syntax, semantic, declaration, vastDeclaration, vastBad, vastSource, invalid: bad.map(item => item.code), ready: art.isReady && art.video.videoWidth > 0 }
}
finally {
art.destroy()
@@ -46,12 +59,17 @@ editorEvents.emit('value', 1)
finally {
model.dispose()
invalid.dispose()
vastInvalid.dispose()
library.dispose()
vastLibrary.dispose()
}
})
expect(result.syntax).toEqual([])
expect(result.semantic).toEqual([])
expect(result.declaration).toEqual([])
expect(result.vastDeclaration).toEqual([])
expect(result.vastBad).toEqual([2554, 2322])
await testInfo.attach('vast-editor-declaration', { contentType: 'text/plain', body: result.vastSource })
expect(result.invalid.length).toBeGreaterThan(0)
expect(result.ready).toBe(true)
})
+6 -2
View File
@@ -4,6 +4,7 @@ import fs from 'node:fs'
import { test } from 'node:test'
import { ESLint } from 'eslint'
import compat from 'typescript-compat'
import { verifyVastContract } from '../refactor/scripts/vast-contract.mjs'
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'
@@ -24,11 +25,14 @@ 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', () => {
test('Chapter and historical workspace VAST editor consumers preserve SDK types, optional Window hook and named results', async () => {
const contract = await verifyVastContract()
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 source = suffix === 'vast'
? contract.sources.get('packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts')
: 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))
+83
View File
@@ -0,0 +1,83 @@
import type { google, ImaSdk } from '@alugha/ima'
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
import type { ArtplayerPluginVastInstance, ArtplayerPluginVastOption, CompatibilityOptions, PublishedContext, RuntimeFactory, RuntimeResult, WorkspaceContext } from 'artplayer-plugin-vast/runtime'
import vast from 'artplayer-plugin-vast/runtime'
declare const art: Artplayer
declare const options: CompatibilityOptions
const registration = vast((context) => {
const player: Player = context.imaPlayer
const container: HTMLDivElement = context.$container
const id: string = context.id
const sdk: ImaSdk = context.ima
const settings: google.ima.AdsRenderingSettings = context.adsRenderingSettings
const playerOptions: PlayerOptions = context.playerOptions
const self: PublishedContext = context
context.id = id
context.$container = container
context.imaPlayer = player
context.init()?.addEventListener('AdStarted', () => {})
const urlResult: void = context.playUrl('/ad.xml', { adTagUrl: '/override.xml', custom: true })
const responseResult: void = context.playRes('<VAST/>')
// @ts-expect-error SDK methods retain actual vendor signatures.
context.imaPlayer.playAds(42)
// @ts-expect-error SDK settings are typed.
context.adsRenderingSettings.enablePreloading = 'yes'
// @ts-expect-error The original media element must remain an element.
context.$container = 'container'
// @ts-expect-error Live container is read-only in both modes.
context.container = container
// @ts-expect-error Terminal core destruction makes init return null.
const initialized: Player = context.init()
void [player, container, id, sdk, settings, playerOptions, self, urlResult, responseResult, initialized]
})
const pending: Promise<RuntimeResult> = registration(art)
// @ts-expect-error Registration is asynchronous.
const synchronous: RuntimeResult = registration(art)
pending.then((result) => {
const name: 'artplayerPluginVast' = result.name
const disposed: void = result.destroy()
const migrated: ArtplayerPluginVastInstance = result
void [name, disposed, migrated]
})
vast()
vast.default()
vast(undefined, { compatibility: 'workspace-1.2' })
vast(async (context) => {
const self: WorkspaceContext = context
const player: Player | null = context.imaPlayer
const container: HTMLDivElement | null = context.$container
const id: string | null = context.id
context.playerOptions.autoResize = false
context.init()?.addEventListener('AdStarted', () => {})
// @ts-expect-error Workspace resource getters may be null.
const nonNull: Player = context.imaPlayer
// @ts-expect-error Workspace fields are getters without setters.
context.imaPlayer = player
// @ts-expect-error Workspace identifiers are getters without setters.
context.id = 'new-id'
// @ts-expect-error Request input remains a string.
context.playUrl(123)
void [self, player, container, id, nonNull]
}, { compatibility: 'workspace-1.2' })
vast((context) => {
const player: Player | null = context.imaPlayer
// @ts-expect-error Dynamic mode cannot promise eager allocation.
const eager: Player = context.imaPlayer
void [player, eager]
}, options)
const callback: ArtplayerPluginVastOption = context => context.init()
vast(callback, { compatibility: 'workspace-1.2' })
const factory: RuntimeFactory = vast.default
// @ts-expect-error Unknown compatibility mode is rejected at runtime.
vast(undefined, { compatibility: 'wrong' })
// @ts-expect-error Callback must be callable when supplied.
vast(1)
// @ts-expect-error A known workspace option cannot provide eager-only context.
vast((context: PublishedContext) => context.id, { compatibility: 'workspace-1.2' })
void [factory, synchronous]