mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
176 lines
6.8 KiB
JavaScript
176 lines
6.8 KiB
JavaScript
import { expect, test } from './fixtures.js'
|
|
|
|
async function setup(page, mobile = false) {
|
|
if (mobile)
|
|
await page.addInitScript(() => Object.defineProperty(navigator, 'userAgent', { configurable: true, value: 'ArtPlayer Android closeout fixture' }))
|
|
await page.goto('/test/player.html?core=candidate&chapter=published')
|
|
await page.evaluate(() => {
|
|
window.Artplayer.SCROLL_TIME = 0
|
|
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, gesture: true })
|
|
window.touch = (target, name, x, y) => {
|
|
const event = new Event(name, { bubbles: true })
|
|
Object.defineProperty(event, 'touches', { value: [{ identifier: 1, pageX: x, pageY: y, clientX: x, clientY: y }] })
|
|
target.dispatchEvent(event)
|
|
}
|
|
})
|
|
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
|
}
|
|
|
|
test('candidate closeout: restoring a removed default hotkey keeps identity and real playback', async ({ page }) => {
|
|
await setup(page)
|
|
expect(await page.evaluate(() => {
|
|
const art = window.art
|
|
const original = art.hotkey.keys.Space[0]
|
|
art.hotkey.remove('Space', original)
|
|
const absent = !art.hotkey.keys.Space
|
|
art.hotkey.init()
|
|
art.hotkey.init()
|
|
art.isFocus = true
|
|
return { absent, count: art.hotkey.keys.Space.length, identity: art.hotkey.keys.Space[0] === original }
|
|
})).toEqual({ absent: true, count: 1, identity: true })
|
|
await page.keyboard.press('Space')
|
|
await expect.poll(() => page.evaluate(() => window.art.playing)).toBe(true)
|
|
await page.evaluate(() => window.art.destroy())
|
|
})
|
|
|
|
test('candidate closeout: a failed native removal remains retryable and releases its record', async ({ page }) => {
|
|
await setup(page)
|
|
expect(await page.evaluate(() => {
|
|
const art = window.art
|
|
const target = document.createElement('div')
|
|
const remove = target.removeEventListener
|
|
const warning = console.warn
|
|
const error = new Error('temporary removal failure')
|
|
let deny = true
|
|
let warnings = 0
|
|
let hits = 0
|
|
console.warn = (message, actual) => {
|
|
if (actual === error)
|
|
warnings++
|
|
}
|
|
target.removeEventListener = function (...args) {
|
|
if (deny)
|
|
throw error
|
|
return remove.apply(this, args)
|
|
}
|
|
try {
|
|
const dispose = art.events.proxy(target, 'example', () => hits++)
|
|
art.events.remove(dispose)
|
|
const retained = art.events.destroyEvents.has(dispose)
|
|
target.dispatchEvent(new Event('example'))
|
|
deny = false
|
|
art.events.remove(dispose)
|
|
target.dispatchEvent(new Event('example'))
|
|
const released = !art.events.destroyEvents.has(dispose)
|
|
art.destroy()
|
|
return { retained, released, warnings, hits }
|
|
}
|
|
finally {
|
|
console.warn = warning
|
|
target.removeEventListener = remove
|
|
}
|
|
})).toEqual({ retained: true, released: true, warnings: 1, hits: 1 })
|
|
})
|
|
|
|
test('candidate closeout: destruction before native add returns cannot leave a late listener', async ({ page }) => {
|
|
await setup(page)
|
|
expect(await page.evaluate(() => {
|
|
const art = window.art
|
|
const target = document.createElement('div')
|
|
const add = target.addEventListener
|
|
let hits = 0
|
|
target.addEventListener = function (...args) {
|
|
art.destroy()
|
|
return add.apply(this, args)
|
|
}
|
|
art.events.proxy(target, 'example', () => hits++)
|
|
target.dispatchEvent(new Event('example'))
|
|
return { hits, remaining: art.events.destroyEvents.size, destroyed: art.isDestroy }
|
|
})).toEqual({ hits: 0, remaining: 0, destroyed: true })
|
|
})
|
|
|
|
test('candidate closeout: window registration failure restores the previous global binding', async ({ page }) => {
|
|
await setup(page)
|
|
expect(await page.evaluate(() => {
|
|
const art = window.art
|
|
const frame = document.createElement('iframe')
|
|
document.body.append(frame)
|
|
const next = frame.contentWindow
|
|
const add = next.addEventListener
|
|
const failure = new Error('window registration failed after add')
|
|
next.addEventListener = function (name, ...args) {
|
|
add.call(this, name, ...args)
|
|
if (name === 'scroll')
|
|
throw failure
|
|
}
|
|
const received = []
|
|
art.on('document:mouseup', event => received.push(event.target === document ? 'old-document' : 'new-document'))
|
|
art.on('window:scroll', event => received.push(event.target === window ? 'old-window' : 'new-window'))
|
|
let sameError = false
|
|
try {
|
|
art.events.bindGlobalEvents({ document: next.document, window: next })
|
|
}
|
|
catch (error) {
|
|
sameError = error === failure
|
|
}
|
|
next.addEventListener = add
|
|
next.document.dispatchEvent(new next.Event('mouseup'))
|
|
next.dispatchEvent(new next.Event('scroll'))
|
|
document.dispatchEvent(new Event('mouseup'))
|
|
window.dispatchEvent(new Event('scroll'))
|
|
art.destroy()
|
|
frame.remove()
|
|
return { sameError, received }
|
|
})).toEqual({ sameError: true, received: ['old-document', 'old-window'] })
|
|
})
|
|
|
|
for (const cause of ['source', 'rotation', 'geometry']) {
|
|
test(`candidate closeout: ${cause} changes cancel the old gesture in the installed player`, async ({ page }) => {
|
|
await setup(page, true)
|
|
expect(await page.evaluate((cause) => {
|
|
const art = window.art
|
|
const video = art.template.$video
|
|
let seeks = 0
|
|
art.on('seek', () => seeks++)
|
|
window.touch(video, 'touchstart', 100, 100)
|
|
if (cause === 'source')
|
|
art.url = '/test/pattern.mp4?closeout=source'
|
|
if (cause === 'rotation')
|
|
art.isRotate = true
|
|
if (cause === 'geometry') {
|
|
art.template.$player.style.setProperty('width', '0px', 'important')
|
|
art.template.$player.style.setProperty('min-width', '0px', 'important')
|
|
}
|
|
const before = seeks
|
|
const width = art.width
|
|
window.touch(video, 'touchmove', cause === 'rotation' ? 100 : 140, cause === 'rotation' ? 140 : 100)
|
|
art.destroy()
|
|
return { newSeeks: seeks - before, zeroWidth: cause !== 'geometry' || width === 0 }
|
|
}, cause)).toEqual({ newSeeks: 0, zeroWidth: true })
|
|
})
|
|
}
|
|
|
|
test('candidate closeout: adopted viewport retains exact gap boundaries', async ({ page }) => {
|
|
await setup(page)
|
|
expect(await page.evaluate(async () => {
|
|
const art = window.art
|
|
const frame = document.createElement('iframe')
|
|
frame.style.cssText = 'height:100px;width:200px'
|
|
document.body.append(frame)
|
|
const container = art.template.$container
|
|
container.style.cssText = 'position:absolute;top:150px;left:0;height:20px;width:20px'
|
|
frame.contentDocument.body.append(container)
|
|
art.events.bindGlobalEvents()
|
|
art.constructor.SCROLL_GAP = 50
|
|
const states = []
|
|
art.on('view', value => states.push(value))
|
|
frame.contentWindow.dispatchEvent(new Event('scroll'))
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
container.style.top = '151px'
|
|
frame.contentWindow.dispatchEvent(new Event('scroll'))
|
|
art.destroy()
|
|
frame.remove()
|
|
return states
|
|
})).toEqual([true, false])
|
|
})
|