mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 22:06:17 -08:00
feat(vue): [EX-02] verify installed consumers and preserve lifecycle contracts
This commit is contained in:
1 parent
4df9689364
commit
d62ab13a35
29 files changed
+2326
-82
No files matched your search
@@ -2,6 +2,15 @@
|
||||
|
||||
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
|
||||
|
||||
`yarn test:vue-consumer` checks the actual Vue example with packed core/Danmuku/
|
||||
Document PiP in an outside-workspace install. It also installs its pinned Vue
|
||||
compiler there, checks all declaration paths for isolation, and runs development
|
||||
and production builds in three engines. `test/vue/` contains strict SFC/type
|
||||
fixtures and a separate plain-JS consumer. Reports under
|
||||
`refactor/.cache/vue-consumer-*` cover updates, remount, KeepAlive, captured errors,
|
||||
native media and real Worker cleanup. `--before` substitutes the recorded old
|
||||
wrapper and must preserve the same lifecycle behavior; it is not expected to fail.
|
||||
|
||||
`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
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import Player from './src/Player.vue'
|
||||
|
||||
const shown = ref(true)
|
||||
const option = { url: '/pattern.mp4', muted: true }
|
||||
const playerStyle = { width: '640px', height: '360px' }
|
||||
// Plain JavaScript keeps the existing event and prop syntax.
|
||||
function getInstance(art) {
|
||||
document.body.dataset.instance = String(art.id)
|
||||
art.on('destroy', () => document.body.dataset.destroyed = 'yes')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button id="toggle" @click="shown = !shown">
|
||||
Toggle player
|
||||
</button>
|
||||
<Player v-if="shown" id="legacy-player" data-consumer="preserved" :option="option" :style="playerStyle" @get-instance="getInstance" />
|
||||
</template>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import type { Option } from 'artplayer'
|
||||
import Artplayer from 'artplayer'
|
||||
import { nextTick, onErrorCaptured, reactive, ref, shallowRef } from 'vue'
|
||||
import Player from './src/Player.vue'
|
||||
|
||||
const shown = ref(true)
|
||||
const sibling = ref(false)
|
||||
const bare = ref(false)
|
||||
const cached = ref(false)
|
||||
const key = ref(0)
|
||||
const player = shallowRef<InstanceType<typeof Player> | null>(null)
|
||||
const option = shallowRef<Partial<Option>>(reactive({ url: '/pattern.mp4', muted: true }))
|
||||
const records: { art: Artplayer, destroyed: number }[] = []
|
||||
const errors: { message: string, same: boolean }[] = []
|
||||
const failure = new Error('vue-consumer-listener-failure')
|
||||
let callbackMode = 'normal'
|
||||
function capture(art: Artplayer) {
|
||||
const record = { art, destroyed: 0 }
|
||||
records.push(record)
|
||||
art.on('destroy', () => record.destroyed++)
|
||||
if (callbackMode === 'throw')
|
||||
throw failure
|
||||
if (callbackMode === 'unmount')
|
||||
shown.value = false
|
||||
}
|
||||
onErrorCaptured((error) => {
|
||||
errors.push({ message: error.message, same: error === failure })
|
||||
return false
|
||||
})
|
||||
const probe = {
|
||||
async act(action: string) {
|
||||
if (action === 'mutate')
|
||||
option.value.url = '/pattern.mp4?mutation'
|
||||
if (action === 'replace')
|
||||
option.value = { url: '/pattern.mp4?replacement', muted: true }
|
||||
if (action === 'key')
|
||||
key.value++
|
||||
if (action === 'siblings')
|
||||
sibling.value = true
|
||||
if (action === 'remove-sibling')
|
||||
sibling.value = false
|
||||
if (action === 'hide')
|
||||
shown.value = false
|
||||
if (action === 'show')
|
||||
shown.value = true
|
||||
if (action === 'bare') {
|
||||
shown.value = false
|
||||
bare.value = true
|
||||
}
|
||||
if (action === 'remove-bare')
|
||||
bare.value = false
|
||||
if (action === 'cache') {
|
||||
shown.value = false
|
||||
cached.value = true
|
||||
}
|
||||
if (action === 'deactivate')
|
||||
cached.value = false
|
||||
if (action === 'activate')
|
||||
cached.value = true
|
||||
if (action === 'throw' || action === 'unmount') {
|
||||
callbackMode = action
|
||||
key.value++
|
||||
shown.value = true
|
||||
}
|
||||
if (action === 'normal')
|
||||
callbackMode = 'normal'
|
||||
if (action === 'cancel') {
|
||||
shown.value = true
|
||||
shown.value = false
|
||||
}
|
||||
await nextTick()
|
||||
},
|
||||
current: () => Artplayer.instances.find(art => art.template.$container.classList.contains('primary-player')) || Artplayer.instances[0],
|
||||
snapshot: () => ({
|
||||
active: Artplayer.instances.length,
|
||||
created: records.length,
|
||||
records: records.map(({ art, destroyed }) => ({ id: art.id, isDestroy: art.isDestroy, destroyed })),
|
||||
errors,
|
||||
exposesArt: Boolean(player.value && 'art' in player.value),
|
||||
componentRef: Boolean(player.value?.$el),
|
||||
players: document.querySelectorAll('.art-video-player').length,
|
||||
videos: document.querySelectorAll('video').length,
|
||||
}),
|
||||
}
|
||||
declare global {
|
||||
interface Window { vueProbe: typeof probe, unmountVue: () => void }
|
||||
}
|
||||
window.vueProbe = probe
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Player v-if="shown" :key="key" ref="player" class="primary-player" :style="{ width: '640px', height: '360px' }" :option="option" @get-instance="capture" />
|
||||
<Player v-if="sibling" :style="{ width: '320px', height: '180px' }" :option="option" @get-instance="capture" />
|
||||
<Player v-if="bare" :style="{ width: '320px', height: '180px' }" :option="option" />
|
||||
<KeepAlive>
|
||||
<Player v-if="cached" :style="{ width: '320px', height: '180px' }" :option="option" @get-instance="capture" />
|
||||
</KeepAlive>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option } from 'artplayer'
|
||||
import type Player from './src/Player.vue'
|
||||
|
||||
type Props = InstanceType<typeof Player>['$props']
|
||||
const option: Partial<Option> = { url: '/pattern.mp4' }
|
||||
export const valid: Props = { option, onGetInstance: (art: Artplayer) => art.pause() }
|
||||
export const optional: Props = { option: {} }
|
||||
// Preserve the historical array-form emits type; do not silently narrow its listener shape.
|
||||
export const oldListener: Props = { option, onGetInstance: (...args: unknown[]) => args.length }
|
||||
// @ts-expect-error Option fields retain the original public types.
|
||||
export const wrongOption: Props = { option: { url: 123 } }
|
||||
// @ts-expect-error The wrapper requires an option object.
|
||||
export const missingOption: Props = {}
|
||||
declare const component: InstanceType<typeof Player>
|
||||
export const publicProps: Props = component.$props
|
||||
// @ts-expect-error Script setup does not expose its private player ref.
|
||||
export const privateRef = component.art
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import Lifecycle from './Lifecycle.vue'
|
||||
|
||||
const app = createApp(Lifecycle)
|
||||
window.unmountVue = () => app.unmount()
|
||||
app.mount('#root')
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import LegacyConsumer from './LegacyConsumer.vue'
|
||||
|
||||
createApp(LegacyConsumer).mount('#root')
|
||||
Reference in new issue
Block a user