mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
252 lines
12 KiB
JavaScript
252 lines
12 KiB
JavaScript
import { expect, test } from './fixtures.js'
|
|
|
|
async function setup(page, core) {
|
|
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
|
await page.evaluate(() => {
|
|
const maps = [
|
|
['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenchange'],
|
|
['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitfullscreenchange'],
|
|
['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitfullscreenchange'],
|
|
['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozfullscreenchange'],
|
|
['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'MSFullscreenChange'],
|
|
]
|
|
window.fullscreenAPI = maps.find(([, exit]) => exit in document)
|
|
window.createPlayer('/test/pattern.mp4')
|
|
window.art.template.$container.id = 'native-fullscreen-main'
|
|
const container = document.createElement('div')
|
|
Object.assign(container.style, { width: '320px', height: '180px' })
|
|
document.body.append(container)
|
|
window.other = new window.Artplayer({ container, url: '/test/pattern.mp4', muted: true })
|
|
window.fullscreenEvents = [[], []]
|
|
window.art.on('fullscreen', value => window.fullscreenEvents[0].push(value))
|
|
window.other.on('fullscreen', value => window.fullscreenEvents[1].push(value))
|
|
document.querySelector('#play').onclick = () => {
|
|
window.art.fullscreen = true
|
|
}
|
|
})
|
|
await expect.poll(() => page.evaluate(() => window.art.isReady && window.other.isReady)).toBe(true)
|
|
expect(await page.evaluate(() => Boolean(window.fullscreenAPI))).toBe(true)
|
|
}
|
|
|
|
test('candidate: void exit waits for the owned video to leave and accepts another owner as completion', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
expect(await page.evaluate(async () => {
|
|
const art = window.art
|
|
const [, exitName, elementName, eventName] = window.fullscreenAPI
|
|
const elementDescriptor = Object.getOwnPropertyDescriptor(document, elementName)
|
|
const originalExit = document[exitName]
|
|
let element = art.template.$video
|
|
Object.defineProperty(document, elementName, { configurable: true, get: () => element })
|
|
document.dispatchEvent(new Event(eventName))
|
|
document[exitName] = () => undefined
|
|
let settled = false
|
|
const result = Object.getOwnPropertyDescriptor(art, 'fullscreen').set(false).then(() => settled = true)
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
const early = settled
|
|
element = window.other.template.$player
|
|
document.dispatchEvent(new Event(eventName))
|
|
await result
|
|
const completed = settled && !art.fullscreen && window.other.fullscreen
|
|
element = null
|
|
document.dispatchEvent(new Event(eventName))
|
|
if (elementDescriptor)
|
|
Object.defineProperty(document, elementName, elementDescriptor)
|
|
else
|
|
delete document[elementName]
|
|
document[exitName] = originalExit
|
|
art.destroy()
|
|
window.other.destroy()
|
|
return { early, completed }
|
|
})).toEqual({ early: false, completed: true })
|
|
})
|
|
|
|
for (const core of ['published', 'candidate']) {
|
|
test(`${core}: native fullscreen belongs to its player and other players cannot exit it`, async ({ page }) => {
|
|
await setup(page, core)
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => document[window.fullscreenAPI[2]] === window.art.template.$player)).toBe(true)
|
|
await expect.poll(() => page.evaluate(() => window.fullscreenEvents[0])).toEqual([true])
|
|
expect(await page.evaluate(() => ({ own: window.art.fullscreen, other: window.other.fullscreen, className: window.other.template.$player.classList.contains('art-fullscreen'), otherEvents: window.fullscreenEvents[1] }))).toEqual({ own: true, other: core === 'published', className: core === 'published', otherEvents: core === 'published' ? [true] : [] })
|
|
await page.evaluate(async () => {
|
|
await Object.getOwnPropertyDescriptor(window.other, 'fullscreen').set.call(window.other, false)
|
|
})
|
|
await expect.poll(() => page.evaluate(() => Boolean(document[window.fullscreenAPI[2]]))).toBe(core === 'candidate')
|
|
await page.evaluate(async () => {
|
|
if (document[window.fullscreenAPI[2]])
|
|
await document[window.fullscreenAPI[1]]()
|
|
window.art.destroy()
|
|
window.other.destroy()
|
|
})
|
|
})
|
|
|
|
test(`${core}: destroyed players stop receiving later native fullscreen events`, async ({ page }) => {
|
|
await setup(page, core)
|
|
await page.evaluate(() => window.other.destroy(false))
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => window.fullscreenEvents[0])).toEqual([true])
|
|
expect(await page.evaluate(() => ({ events: window.fullscreenEvents[1], className: window.other.template.$player.classList.contains('art-fullscreen') }))).toEqual({ events: core === 'published' ? [true] : [], className: core === 'published' })
|
|
await page.evaluate(async () => {
|
|
await document[window.fullscreenAPI[1]]()
|
|
window.art.destroy()
|
|
})
|
|
})
|
|
|
|
test(`${core}: rejected native fullscreen request removes its temporary change listener`, async ({ page }) => {
|
|
await setup(page, core)
|
|
expect(await page.evaluate(async () => {
|
|
const art = window.art
|
|
const [request, , , change] = window.fullscreenAPI
|
|
const add = document.addEventListener
|
|
const remove = document.removeEventListener
|
|
const active = new Set()
|
|
document.addEventListener = function (name, callback, options) {
|
|
if (name === change)
|
|
active.add(callback)
|
|
return add.call(this, name, callback, options)
|
|
}
|
|
document.removeEventListener = function (name, callback, options) {
|
|
if (name === change)
|
|
active.delete(callback)
|
|
return remove.call(this, name, callback, options)
|
|
}
|
|
const failure = new Error('controlled fullscreen rejection')
|
|
art.template.$player[request] = () => Promise.reject(failure)
|
|
const rejected = await Object.getOwnPropertyDescriptor(art, 'fullscreen').set.call(art, true).then(() => false, error => error === failure)
|
|
const result = { rejected, leaked: active.size }
|
|
document.addEventListener = add
|
|
document.removeEventListener = remove
|
|
art.destroy()
|
|
window.other.destroy()
|
|
return result
|
|
})).toEqual({ rejected: true, leaked: core === 'published' ? 1 : 0 })
|
|
})
|
|
}
|
|
|
|
test('candidate: fullscreen assignment rejection shows notice without an unhandled rejection', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
await page.evaluate(() => {
|
|
const [request] = window.fullscreenAPI
|
|
window.art.template.$player[request] = () => Promise.reject(new Error('controlled assignment denial'))
|
|
window.art.fullscreen = true
|
|
})
|
|
await expect(page.locator('#native-fullscreen-main .art-notice')).toContainText('controlled assignment denial')
|
|
await page.evaluate(() => {
|
|
window.art.destroy()
|
|
window.other.destroy()
|
|
})
|
|
})
|
|
|
|
test('candidate: reentrant entry preserves real fullscreen UI and event order', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
await page.evaluate(() => {
|
|
window.fullscreenOrder = []
|
|
window.art.on('fullscreen', (value) => {
|
|
if (value) {
|
|
window.fullscreenOrder.push(['fullscreen', window.art.template.$player.classList.contains('art-fullscreen')])
|
|
window.art.fullscreen = true
|
|
}
|
|
})
|
|
window.art.on('resize', () => {
|
|
if (window.art.fullscreen)
|
|
window.fullscreenOrder.push(['resize', window.art.template.$player.classList.contains('art-fullscreen')])
|
|
})
|
|
})
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => window.art.fullscreen && window.art.template.$player.classList.contains('art-fullscreen'))).toBe(true)
|
|
expect(await page.evaluate(() => window.fullscreenOrder.slice(0, 2))).toEqual([['fullscreen', false], ['resize', true]])
|
|
await page.evaluate(async () => {
|
|
await document[window.fullscreenAPI[1]]()
|
|
window.art.destroy()
|
|
window.other.destroy()
|
|
})
|
|
})
|
|
|
|
for (const mode of ['promise-exit', 'promise-destroy', 'void-exit', 'void-destroy']) {
|
|
test(`candidate: pending native entry ${mode} releases a late own entry`, async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
expect(await page.evaluate(async (mode) => {
|
|
const art = window.art
|
|
const [request, exit, element, change] = window.fullscreenAPI
|
|
const originalExit = document[exit]
|
|
const originalElement = Object.getOwnPropertyDescriptor(document, element)
|
|
let current = null
|
|
let complete
|
|
let exits = 0
|
|
Object.defineProperty(document, element, { configurable: true, get: () => current })
|
|
art.template.$player[request] = () => {
|
|
if (mode.startsWith('promise'))
|
|
return new Promise(resolve => complete = resolve)
|
|
complete = () => document.dispatchEvent(new Event(change))
|
|
}
|
|
document[exit] = () => {
|
|
exits++
|
|
current = null
|
|
document.dispatchEvent(new Event(change))
|
|
return Promise.resolve()
|
|
}
|
|
const setter = Object.getOwnPropertyDescriptor(art, 'fullscreen').set
|
|
const pending = setter.call(art, true)
|
|
if (mode.endsWith('destroy'))
|
|
art.destroy(false)
|
|
else
|
|
await setter.call(art, false)
|
|
await pending
|
|
current = art.template.$player
|
|
complete()
|
|
// Flush the request's native Promise observer and setter settlement chain.
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
const result = { exits, fullscreen: art.fullscreen, className: art.template.$player.classList.contains('art-fullscreen'), events: window.fullscreenEvents[0] }
|
|
art.destroy()
|
|
window.other.destroy()
|
|
document[exit] = originalExit
|
|
if (originalElement)
|
|
Object.defineProperty(document, element, originalElement)
|
|
else
|
|
delete document[element]
|
|
return result
|
|
}, mode)).toEqual({ exits: 1, fullscreen: false, className: false, events: [] })
|
|
})
|
|
}
|
|
|
|
test('candidate: cancelling an actual native request in the initiating click releases fullscreen', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
await page.evaluate(() => {
|
|
window.entrySettled = false
|
|
document.querySelector('#play').onclick = () => {
|
|
const setter = Object.getOwnPropertyDescriptor(window.art, 'fullscreen').set
|
|
const entering = setter.call(window.art, true)
|
|
window.art.fullscreen = false
|
|
void entering.then(() => window.entrySettled = true)
|
|
}
|
|
})
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => window.entrySettled && !document[window.fullscreenAPI[2]])).toBe(true)
|
|
await page.evaluate(() => {
|
|
window.art.destroy()
|
|
window.other.destroy()
|
|
})
|
|
})
|
|
|
|
test('candidate: rejected exit retains actual fullscreen state and can be retried', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
await page.click('#play')
|
|
await expect.poll(() => page.evaluate(() => window.art.template.$player.classList.contains('art-fullscreen'))).toBe(true)
|
|
expect(await page.evaluate(async () => {
|
|
const [, exit] = window.fullscreenAPI
|
|
const originalExit = document[exit]
|
|
const failure = new Error('controlled exit denial')
|
|
document[exit] = () => Promise.reject(failure)
|
|
const setter = Object.getOwnPropertyDescriptor(window.art, 'fullscreen').set
|
|
const rejected = await setter.call(window.art, false).then(() => false, error => error === failure)
|
|
const result = { rejected, fullscreen: window.art.fullscreen, className: window.art.template.$player.classList.contains('art-fullscreen') }
|
|
document[exit] = originalExit
|
|
await setter.call(window.art, false)
|
|
return result
|
|
})).toEqual({ rejected: true, fullscreen: true, className: true })
|
|
await expect.poll(() => page.evaluate(() => window.art.fullscreen || window.art.template.$player.classList.contains('art-fullscreen'))).toBe(false)
|
|
await page.evaluate(() => {
|
|
window.art.destroy()
|
|
window.other.destroy()
|
|
})
|
|
})
|