mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-25 08:01:20 -08:00
fix(thumbnail): [PKG-TOOL-THUMB-04] supply public types and installed module entries
This commit is contained in:
1 parent
15485b6420
commit
3bff236145
33 files changed
+2466
-164
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { thumbnailCandidate } from '../helpers/thumbnail.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const candidate = await thumbnailCandidate()
|
||||
|
||||
test('Monaco consumes Thumbnail global declarations and runs the actual typed constructor and cleanup', async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.addScriptTag({ content: candidate.code })
|
||||
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
|
||||
const result = await page.evaluate(async () => {
|
||||
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(['artplayer', 'artplayer-tool-thumbnail', 'artplayer-proxy-mediabunny'].map(async (name) => {
|
||||
const uri = `file:///${name}.d.ts`
|
||||
const source = await (await fetch(`/assets/ts/${name}.d.ts`)).text()
|
||||
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
|
||||
}))
|
||||
const model = api.editor.createModel(`
|
||||
const input = document.createElement('input'); input.type = 'file'; document.body.appendChild(input);
|
||||
const options: ArtplayerToolThumbnail.Option = { fileInput: input, number: 10 };
|
||||
const tool: ArtplayerToolThumbnail = new ArtplayerToolThumbnail(options);
|
||||
let last = 0;
|
||||
tool.on('custom', (value: number) => { last = value; });
|
||||
tool.emit('custom', 7);
|
||||
tool.on('update', (url, progress) => url + progress.toFixed());
|
||||
const video: HTMLVideoElement = tool.video;
|
||||
tool.destroy(); tool.destroy(); input.remove();
|
||||
function mediaTypes(player: artplayerProxyMediabunny.MediaBunnyPlayer): number | undefined { return player.mediabunny?.currentTime; }
|
||||
`, 'typescript', api.Uri.parse('file:///thumbnail-consumer.ts'))
|
||||
const invalid = api.editor.createModel("new ArtplayerToolThumbnail({ fileInput: '#input' });", 'typescript', api.Uri.parse('file:///thumbnail-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 declaration = (await Promise.all(libraries.map(library => worker.getSemanticDiagnostics(library.uri)))).flat()
|
||||
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
|
||||
if (syntax.length || semantic.length || declaration.length)
|
||||
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), executed: false }
|
||||
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 Thumbnail fixture')
|
||||
// eslint-disable-next-line no-new-func -- Run actual code emitted by the local Monaco worker.
|
||||
const actual = new Function(`${script.text}\nreturn { last, connected: video.isConnected, inputConnected: input.isConnected, processing: tool.processing }`)()
|
||||
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), executed: true, actual }
|
||||
}
|
||||
finally {
|
||||
model.dispose()
|
||||
invalid.dispose()
|
||||
for (const library of libraries)
|
||||
library.handle.dispose()
|
||||
}
|
||||
})
|
||||
expect(result.syntax).toEqual([])
|
||||
expect(result.semantic).toEqual([])
|
||||
expect(result.declaration).toEqual([])
|
||||
expect(result.invalid).toEqual([2322])
|
||||
expect(result.executed).toBe(true)
|
||||
expect(result.actual).toEqual({ last: 7, connected: false, inputConnected: false, processing: false })
|
||||
await testInfo.attach('thumbnail-editor', { contentType: 'application/json', body: JSON.stringify({ candidate: candidate.name, sha256: hash(candidate.code), declarations: Object.fromEntries(['artplayer', 'artplayer-tool-thumbnail', 'artplayer-proxy-mediabunny'].map(name => [name, hash(fs.readFileSync(`docs/assets/ts/${name}.d.ts`))])), result, scope: 'Real local Monaco worker, typed constructor/custom event and native DOM cleanup; MediaBunny relative type bundle also compiles. This does not test file decoding or the complete demo/player integration.' }) })
|
||||
})
|
||||
@@ -129,3 +129,24 @@ test('Thumbnail typed facade retains synchronous validation, option mutation and
|
||||
assert.equal(tool.errorHandle(true, 'unused'), undefined)
|
||||
tool.destroy()
|
||||
})
|
||||
|
||||
test('Thumbnail error event preserves arbitrary thrown message values and Promise rejection identity', async () => {
|
||||
for (const failure of [{ message: 7 }, { message: { detail: 'failure' } }, 3, null]) {
|
||||
const { tool } = create(candidate)
|
||||
tool.setup({ number: 10 })
|
||||
tool.file = { name: 'sample.mp4' }
|
||||
tool.video.duration = 100
|
||||
const errors = []
|
||||
tool.on('error', value => errors.push(value))
|
||||
tool.on('update', () => {
|
||||
throw failure
|
||||
})
|
||||
const pending = tool.start()
|
||||
await Promise.resolve()
|
||||
tool.video.oncanplay()
|
||||
await assert.rejects(pending, error => error === failure)
|
||||
assert.deepEqual(errors, [failure?.message])
|
||||
assert.equal(tool.processing, false)
|
||||
tool.destroy()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
// eslint-disable-next-line ts/no-require-imports -- Test the actual CommonJS constructor without interop helpers.
|
||||
import Thumbnail = require('artplayer-tool-thumbnail')
|
||||
// eslint-disable-next-line ts/no-require-imports -- The legacy subpath also directly exports its constructor.
|
||||
import Legacy = require('artplayer-tool-thumbnail/legacy')
|
||||
|
||||
const option: Thumbnail.Option = { fileInput: document.createElement('input') }
|
||||
const tool: Thumbnail = new Thumbnail(option)
|
||||
const legacy: Thumbnail = new Legacy(option)
|
||||
const pending: Promise<void> = tool.start()
|
||||
tool.on('update', (url, progress) => url + progress.toFixed())
|
||||
void [legacy, pending]
|
||||
// @ts-expect-error No self-default constructor is exported.
|
||||
const WrongAlias = Thumbnail.default
|
||||
void WrongAlias
|
||||
@@ -11,7 +11,7 @@ tool.once('file', function (file) {
|
||||
this.name = file.name
|
||||
}, { name: '' })
|
||||
tool.on('error', (message) => {
|
||||
const value: string | undefined = message
|
||||
const value: unknown = message
|
||||
void value
|
||||
})
|
||||
tool.on('custom', (...payload) => {
|
||||
@@ -19,6 +19,7 @@ tool.on('custom', (...payload) => {
|
||||
void values
|
||||
})
|
||||
tool.emit(Symbol('extension'), 'value')
|
||||
tool.on('custom-number', (value: number) => value.toFixed())
|
||||
const pending: Promise<void> = tool.start()
|
||||
const point: { time: number, x: number, y: number } | undefined = tool.creatScreenshotDate()[0]
|
||||
const canvas: HTMLCanvasElement = tool.creatCanvas()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type Definition from 'artplayer-tool-thumbnail'
|
||||
import Implementation from '../../packages/artplayer-tool-thumbnail/src'
|
||||
|
||||
const constructor: typeof Definition = Implementation
|
||||
const RuntimeConstructor: typeof Implementation = constructor
|
||||
const value: Definition = new RuntimeConstructor({ fileInput: document.createElement('input') })
|
||||
value.on('custom', (count: number) => count.toFixed())
|
||||
void value
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { EventArgs, Option, ScreenshotPoint } from 'artplayer-tool-thumbnail'
|
||||
import Thumbnail from 'artplayer-tool-thumbnail'
|
||||
import Legacy from 'artplayer-tool-thumbnail/legacy'
|
||||
|
||||
const input = document.createElement('input')
|
||||
const option: Option = { fileInput: input, number: 10, width: 80, custom: { id: 1 } }
|
||||
const tool: Thumbnail = new Thumbnail(option)
|
||||
const legacy: Thumbnail = new Legacy(option)
|
||||
const duration: number = tool.duration
|
||||
const file: File | undefined = tool.file
|
||||
const url: string | undefined = tool.thumbnailUrl
|
||||
const pending: Promise<void> = tool.start()
|
||||
const canvas: HTMLCanvasElement = tool.creatCanvas()
|
||||
const points: ScreenshotPoint[] = tool.creatScreenshotDate()
|
||||
const extension: unknown = tool.option.custom
|
||||
const progress: EventArgs<'update'> = ['blob:sheet', 0.5]
|
||||
tool.on('file', file => file.name)
|
||||
tool.on('video', video => video.videoWidth)
|
||||
tool.on('canvas', canvas => canvas.width)
|
||||
tool.on('update', (url, progress) => url + progress.toFixed())
|
||||
tool.on('error', (message) => {
|
||||
if (typeof message === 'string')
|
||||
message.toUpperCase()
|
||||
})
|
||||
tool.on('custom', (count: number, label: string) => label + count.toFixed())
|
||||
tool.once(Symbol('custom'), (value: { id: number }) => value.id)
|
||||
tool.on('update', function (url, progress) {
|
||||
this.last = `${url}:${progress}`
|
||||
}, { last: '' })
|
||||
tool.emit('update', ...progress).emit('custom', 1, 'one')
|
||||
tool.off('file').setup({ width: 100 }).download()
|
||||
tool.loadVideo(input.files?.[0])
|
||||
tool.loadVideo(null)
|
||||
const defaults: Thumbnail.SheetOptions = Thumbnail.DEFAULTS
|
||||
const end: void = tool.destroy()
|
||||
void [legacy, duration, file, url, pending, canvas, points, extension, defaults, end]
|
||||
|
||||
// @ts-expect-error Numeric width is required.
|
||||
tool.setup({ width: '80' })
|
||||
// @ts-expect-error Selectors have never been resolved by the tool.
|
||||
const wrongInput = new Thumbnail({ fileInput: '#input' })
|
||||
// @ts-expect-error File input is not a media source URL.
|
||||
tool.loadVideo('video.mp4')
|
||||
// @ts-expect-error Progress is numeric and follows the URL.
|
||||
tool.emit('update', 0.5, 'blob:sheet')
|
||||
// @ts-expect-error File callbacks receive files, not media elements.
|
||||
tool.on('file', (value: HTMLVideoElement) => value.duration)
|
||||
// @ts-expect-error Completion returns no URL.
|
||||
const wrongResult: Promise<string> = tool.start()
|
||||
// @ts-expect-error The direct constructor has no runtime .default self-alias.
|
||||
const WrongAlias = Thumbnail.default
|
||||
// @ts-expect-error density is absent until extraction preflight.
|
||||
const earlyDensity: number = tool.density
|
||||
// @ts-expect-error Error listeners must narrow arbitrary thrown message values.
|
||||
tool.on('error', (message: string) => message.length)
|
||||
// @ts-expect-error Built-in done does not carry a file.
|
||||
tool.emit('done', input.files?.[0])
|
||||
void [wrongInput, wrongResult, WrongAlias, earlyDensity]
|
||||
Reference in new issue
Block a user