mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-28 02:01:20 -08:00
refactor(core): CORE-18 type builtins and own prompt lifetimes
This commit is contained in:
1 parent
a842058136
commit
98a1800032
55 files changed
+26511
-650
No files matched your search
@@ -0,0 +1,193 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
async function setup(page, core, mobile = false) {
|
||||
await page.addInitScript(mobile => Object.defineProperty(navigator, 'userAgent', { configurable: true, value: mobile ? 'ArtPlayer Android info fixture' : 'ArtPlayer Desktop info fixture' }), mobile)
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => window.createPlayer('/test/pattern.mp4'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: mask preserves observations before and after its destroy handler`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
expect(await page.evaluate(() => {
|
||||
const seen = []
|
||||
const art = new window.Artplayer({ container: '.player', url: '', layers: [{
|
||||
name: 'observer',
|
||||
html: '',
|
||||
mounted() {
|
||||
this.on('destroy', () => seen.push(['before', this.template.$state.querySelector('.art-icon-error').style.display]))
|
||||
},
|
||||
}] })
|
||||
art.on('destroy', () => seen.push(['after', art.template.$state.querySelector('.art-icon-error').style.display]))
|
||||
art.destroy(false)
|
||||
return seen
|
||||
})).toEqual([['before', 'none'], ['after', '']])
|
||||
})
|
||||
|
||||
test(`${core}: info preserves fields, native media text and close visibility`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
art.template.$video.volume = 0.4
|
||||
art.info.init()
|
||||
const volume = art.template.$infoPanel.querySelector('[data-video="volume"]').textContent
|
||||
const source = art.template.$infoPanel.querySelector('[data-video="currentSrc"]').textContent === art.template.$video.currentSrc
|
||||
art.info.show = true
|
||||
art.template.$infoClose.click()
|
||||
const result = { fields: Object.keys(art.info), prototype: Object.getOwnPropertyNames(Object.getPrototypeOf(art.info)), volume, source, visible: art.info.show }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ fields: ['id', 'art', 'cache', 'add', 'remove', 'update', 'name'], prototype: ['constructor', 'init'], volume: '0.40', source: true, visible: false })
|
||||
})
|
||||
|
||||
test(`${core}: info retains native nullable string conversion for custom media fields`, async ({ page }) => {
|
||||
await setup(page, core, true)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const item = document.createElement('span')
|
||||
item.dataset.video = 'fixtureValue'
|
||||
art.template.$infoPanel.append(item)
|
||||
const values = [null, undefined, true, 1.234, Number.NaN, Infinity, { toString: () => 'custom' }]
|
||||
const result = values.map((value) => {
|
||||
art.template.$video.fixtureValue = value
|
||||
art.info.init()
|
||||
return item.textContent
|
||||
})
|
||||
art.template.$video.fixtureValue = Symbol('not text')
|
||||
let failed
|
||||
try {
|
||||
art.info.init()
|
||||
}
|
||||
catch (error) {
|
||||
failed = error.name
|
||||
}
|
||||
art.destroy()
|
||||
return { result, failed }
|
||||
})).toEqual({ result: ['', '', 'true', '1.23', 'NaN', 'Infinity', 'custom'], failed: 'TypeError' })
|
||||
})
|
||||
|
||||
test(`${core}: repeated info init owns only one new timer and close handler`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const set = window.setTimeout
|
||||
const clear = window.clearTimeout
|
||||
const pending = new Set()
|
||||
window.setTimeout = function (callback, delay, ...args) {
|
||||
const timer = set.call(this, callback, delay, ...args)
|
||||
if (delay === 24736)
|
||||
pending.add(timer)
|
||||
return timer
|
||||
}
|
||||
window.clearTimeout = function (timer) {
|
||||
pending.delete(timer)
|
||||
return clear.call(this, timer)
|
||||
}
|
||||
art.constructor.INFO_LOOP_TIME = 24736
|
||||
art.info.init()
|
||||
art.info.init()
|
||||
let closed = 0
|
||||
art.on('info', state => !state && closed++)
|
||||
art.info.show = true
|
||||
art.template.$infoClose.click()
|
||||
const timers = pending.size
|
||||
art.destroy()
|
||||
const result = { timers, closed, remaining: pending.size }
|
||||
window.setTimeout = set
|
||||
window.clearTimeout = clear
|
||||
return result
|
||||
})).toEqual({ timers: core === 'candidate' ? 1 : 2, closed: core === 'candidate' ? 1 : 3, remaining: 0 })
|
||||
})
|
||||
|
||||
test(`${core}: info mobile construction waits for an explicit init`, async ({ page }) => {
|
||||
await setup(page, core, true)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const node = art.template.$infoPanel.querySelector('[data-video="volume"]')
|
||||
const before = node.textContent
|
||||
art.template.$video.volume = 0.4
|
||||
art.info.init()
|
||||
const after = node.textContent
|
||||
art.destroy()
|
||||
return { before, after }
|
||||
})).toEqual({ before: '', after: '0.40' })
|
||||
})
|
||||
|
||||
test(`${core}: mask click plays native video and destruction displays the terminal icon`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
await page.evaluate(() => window.art.template.$state.click())
|
||||
await expect.poll(() => page.evaluate(() => window.art.playing)).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const state = art.template.$state.querySelector('.art-icon-state')
|
||||
const error = art.template.$state.querySelector('.art-icon-error')
|
||||
art.loading.show = true
|
||||
const loading = art.loading.show
|
||||
const icon = Boolean(art.template.$loading.querySelector('.art-icon-loading'))
|
||||
art.destroy(false)
|
||||
return { loading, icon, state: state.style.display, error: error.style.display }
|
||||
})).toEqual({ loading: true, icon: true, state: 'none', error: '' })
|
||||
})
|
||||
}
|
||||
|
||||
test('candidate: info destruction during a media getter prevents text writes and late init', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
const result = await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const node = art.template.$infoPanel.querySelector('[data-video="volume"]')
|
||||
const before = node.textContent
|
||||
Object.defineProperty(art.template.$video, 'volume', { configurable: true, get() {
|
||||
art.destroy(false)
|
||||
return 0.8
|
||||
} })
|
||||
art.info.init()
|
||||
const after = node.textContent
|
||||
art.info.init()
|
||||
return { before, after, destroyed: art.isDestroy }
|
||||
})
|
||||
expect(result.after).toBe(result.before)
|
||||
expect(result.destroyed).toBe(true)
|
||||
})
|
||||
|
||||
test('candidate: reentrant info text conversion cannot write after player destruction', async ({ page }) => {
|
||||
await setup(page, 'candidate', true)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const item = document.createElement('span')
|
||||
item.dataset.video = 'fixtureValue'
|
||||
item.textContent = 'before'
|
||||
art.template.$infoPanel.append(item)
|
||||
art.template.$video.fixtureValue = { toString() {
|
||||
art.destroy(false)
|
||||
return 'late'
|
||||
} }
|
||||
art.info.init()
|
||||
return { text: item.textContent, destroyed: art.isDestroy }
|
||||
})).toEqual({ text: 'before', destroyed: true })
|
||||
})
|
||||
|
||||
test('candidate: mask finalization follows an earlier throwing observer without replacing its error', async ({ page }) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
expect(await page.evaluate(() => {
|
||||
const failure = new Error('early observer failed')
|
||||
const art = new window.Artplayer({ container: '.player', url: '', layers: [{
|
||||
name: 'observer',
|
||||
html: '',
|
||||
mounted() {
|
||||
this.on('destroy', () => {
|
||||
throw failure
|
||||
})
|
||||
},
|
||||
}] })
|
||||
let original = false
|
||||
try {
|
||||
art.destroy(false)
|
||||
}
|
||||
catch (error) {
|
||||
original = error === failure
|
||||
}
|
||||
return { original, destroyed: art.isDestroy, state: art.template.$state.querySelector('.art-icon-state').style.display, error: art.template.$state.querySelector('.art-icon-error').style.display }
|
||||
})).toEqual({ original: true, destroyed: true, state: 'none', error: '' })
|
||||
})
|
||||
Reference in new issue
Block a user