mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-23 22:01:20 -08:00
fix(react): [EX-01] verify installed consumers and clean up callback failures
This commit is contained in:
1 parent
3e39fc745f
commit
e0d785662d
25 files changed
+3539
-98
No files matched your search
@@ -2,6 +2,14 @@
|
||||
|
||||
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
|
||||
|
||||
`yarn test:react-consumer` installs packed core/Danmuku/Document PiP outside the
|
||||
workspace and checks the actual React example's strict TSX, development and
|
||||
production builds, and native lifecycle/playback in three engines. Its fixture
|
||||
is in `test/react/`; unique reports and frozen consumer locks are retained under
|
||||
`refactor/.cache/react-consumer-*`. The `--before` control intentionally fails at
|
||||
the recorded old wrapper's callback-exception leak. See the example README for
|
||||
the compatibility contract and the capabilities covered by other package tasks.
|
||||
|
||||
`yarn test:auto-thumbnail-types` checks the preserved npm 1.1.0 declaration,
|
||||
accurate `/runtime` Promise types, exact rejected consumer lines, and generated
|
||||
editor globals. It also runs in `test:auto-thumbnail` and `test:baseline`.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option } from 'artplayer'
|
||||
import type { ComponentProps } from 'react'
|
||||
import Player from './src/Player'
|
||||
|
||||
const option: Partial<Option> = { url: '/pattern.mp4' }
|
||||
const props: ComponentProps<typeof Player> = {
|
||||
option,
|
||||
getInstance(art: Artplayer) { art.pause() },
|
||||
style: { width: 640 },
|
||||
className: 'consumer-container',
|
||||
onClick(event) { event.currentTarget.dataset.clicked = 'yes' },
|
||||
}
|
||||
export const valid = <Player {...props} />
|
||||
export const optional = <Player option={{}} />
|
||||
// @ts-expect-error The historical callback receives a player, not a media element.
|
||||
export const wrongCallback = <Player option={option} getInstance={(video: HTMLVideoElement) => video.pause()} />
|
||||
// @ts-expect-error Native div attributes retain their original types.
|
||||
export const wrongStyle = <Player option={option} style="width: 640px" />
|
||||
// @ts-expect-error Player options still enforce field types.
|
||||
export const wrongOption = <Player option={{ url: 123 }} />
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import Artplayer from 'artplayer'
|
||||
import { Component, StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import Player from './src/Player'
|
||||
|
||||
const records: { art: Artplayer, destroyed: number }[] = []
|
||||
const errors: { message: string, same: boolean }[] = []
|
||||
const container = document.getElementById('root')
|
||||
if (!container)
|
||||
throw new Error('Missing React fixture container')
|
||||
const root = createRoot(container)
|
||||
const option = { url: '/pattern.mp4', muted: true }
|
||||
const changedOption = { ...option, volume: 0.2 }
|
||||
const failure = new Error('react-consumer-callback-failure')
|
||||
function capture(art: Artplayer) {
|
||||
const record = { art, destroyed: 0 }
|
||||
records.push(record)
|
||||
art.on('destroy', () => record.destroyed++)
|
||||
}
|
||||
function changedCapture(art: Artplayer) {
|
||||
capture(art)
|
||||
}
|
||||
function fail(art: Artplayer) {
|
||||
capture(art)
|
||||
throw failure
|
||||
}
|
||||
function failWithCleanupError(art: Artplayer) {
|
||||
const destroy = art.destroy.bind(art)
|
||||
art.destroy = (...args) => {
|
||||
destroy(...args)
|
||||
throw new Error('secondary-cleanup-error')
|
||||
}
|
||||
fail(art)
|
||||
}
|
||||
class Boundary extends Component<{ children: ReactNode }, { failed: boolean }> {
|
||||
state = { failed: false }
|
||||
static getDerivedStateFromError() { return { failed: true } }
|
||||
componentDidCatch(error: Error) { errors.push({ message: error.message, same: error === failure }) }
|
||||
render() { return this.state.failed ? <div id="caught">caught</div> : this.props.children }
|
||||
}
|
||||
function render(mode = 'normal') {
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<Boundary key={mode.startsWith('throw') ? mode : 'healthy'}>
|
||||
{mode !== 'empty' && (
|
||||
<Player
|
||||
key="primary"
|
||||
id="player"
|
||||
className="consumer-container"
|
||||
data-consumer="preserved"
|
||||
style={{ width: 640, height: 360 }}
|
||||
option={mode === 'option' ? changedOption : option}
|
||||
getInstance={mode === 'throw-cleanup' ? failWithCleanupError : mode === 'throw' ? fail : mode === 'callback' ? changedCapture : mode === 'optional' ? undefined : capture}
|
||||
/>
|
||||
)}
|
||||
{mode === 'siblings' && <Player key="sibling" id="sibling" style={{ width: 320, height: 180 }} option={option} getInstance={capture} />}
|
||||
</Boundary>
|
||||
</StrictMode>,
|
||||
)
|
||||
}
|
||||
const probe = {
|
||||
render,
|
||||
snapshot: () => ({
|
||||
active: Artplayer.instances.length,
|
||||
created: records.length,
|
||||
records: records.map(({ art, destroyed }) => ({ id: art.id, isDestroy: art.isDestroy, destroyed })),
|
||||
plugins: Artplayer.instances.map(art => Object.keys(art.plugins)),
|
||||
errors,
|
||||
videos: document.querySelectorAll('video').length,
|
||||
players: document.querySelectorAll('.art-video-player').length,
|
||||
}),
|
||||
current: () => Artplayer.instances[0],
|
||||
}
|
||||
declare global {
|
||||
interface Window { probe: typeof probe }
|
||||
}
|
||||
window.probe = probe
|
||||
render()
|
||||
Reference in new issue
Block a user