Files
ArtPlayer/test/browser/input-closeout.spec.js

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])
})