Files
ArtPlayer/test/browser/display-web.spec.js

261 lines
11 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(() => window.createPlayer('/test/pattern.mp4'))
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
}
test('candidate: adopted player enters its current document body and restores placement and focus', async ({ page }) => {
await setup(page, 'candidate')
const result = await page.evaluate(() => {
const frame = document.createElement('iframe')
document.body.append(frame)
const target = frame.contentDocument
const wrapper = target.createElement('div')
const sibling = target.createElement('span')
const probe = target.createElement('button')
const player = window.art.template.$player
target.body.append(wrapper)
wrapper.append(player, sibling)
player.append(probe)
probe.focus()
window.Artplayer.FULLSCREEN_WEB_IN_BODY = true
window.art.fullscreenWeb = true
const entered = player.parentNode === target.body && player.ownerDocument === target
const focused = target.activeElement === probe
window.art.fullscreenWeb = false
const restored = player.parentNode === wrapper && player.nextSibling === sibling && target.activeElement === probe
window.art.destroy()
frame.remove()
return { entered, focused, restored }
})
expect(result).toEqual({ entered: true, focused: true, restored: true })
})
for (const removeHtml of [false, true]) {
for (const movedBeforeThrow of [false, true]) {
test(`candidate: failed destroy restoration cannot orphan the player removeHtml=${removeHtml} movedBeforeThrow=${movedBeforeThrow}`, async ({ page }) => {
await setup(page, 'candidate')
expect(await page.evaluate(({ removeHtml, movedBeforeThrow }) => {
const art = window.art
const { $player: player, $container: container } = art.template
art.constructor.FULLSCREEN_WEB_IN_BODY = true
art.fullscreenWeb = true
const original = container.insertBefore
const failure = new Error('destroy restore denied')
container.insertBefore = function (node, anchor) {
if (movedBeforeThrow)
original.call(this, node, anchor)
throw failure
}
let sameError = false
try {
art.destroy(removeHtml)
}
catch (error) {
sameError = error === failure
}
container.insertBefore = original
return { sameError, orphan: player.isConnected && !container.contains(player), retained: container.contains(player), destroyed: art.isDestroy, registered: art.constructor.instances.includes(art) }
}, { removeHtml, movedBeforeThrow })).toEqual({ sameError: true, orphan: false, retained: movedBeforeThrow && !removeHtml, destroyed: true, registered: false })
})
}
}
test('candidate: web fullscreen listener can exit without stale resize or overwritten restoration', async ({ page }) => {
await setup(page, 'candidate')
const result = await page.evaluate(() => {
const art = window.art
const player = art.template.$player
player.style.width = '75%'
const style = player.style.cssText
const events = []
art.on('fullscreenWeb', (value) => {
events.push(value)
if (value)
art.fullscreenWeb = false
})
art.on('resize', () => events.push('resize'))
art.fullscreenWeb = true
const result = { events, active: art.fullscreenWeb, restored: player.style.cssText === style }
art.destroy()
return result
})
expect(result).toEqual({ events: [true, false, 'resize'], active: false, restored: true })
})
test('candidate: web fullscreen listener destroy restores ownership without post-destroy resize', async ({ page }) => {
await setup(page, 'candidate')
expect(await page.evaluate(() => {
const art = window.art
const { $player: player, $container: container } = art.template
art.constructor.FULLSCREEN_WEB_IN_BODY = true
const style = player.style.cssText
const events = []
art.on('fullscreenWeb', () => art.destroy(false))
art.on('destroy', () => events.push('destroy'))
art.on('resize', () => events.push('resize'))
art.fullscreenWeb = true
art.fullscreenWeb = true
return { events, retained: container.contains(player), style: player.style.cssText === style, fullscreen: player.classList.contains('art-fullscreen-web') }
})).toEqual({ events: ['destroy'], retained: true, style: true, fullscreen: false })
})
test('candidate: a failed body move restores style and keeps its original error', async ({ page }) => {
await setup(page, 'candidate')
expect(await page.evaluate(() => {
const art = window.art
const { $player: player, $container: container } = art.template
const style = player.getAttribute('style')
art.constructor.FULLSCREEN_WEB_IN_BODY = true
const append = document.body.appendChild
const failure = new Error('controlled body insertion failure')
document.body.appendChild = function (node) {
if (node === player)
throw failure
return append.call(this, node)
}
let sameError = false
try {
art.fullscreenWeb = true
}
catch (error) {
sameError = error === failure
}
document.body.appendChild = append
const result = { sameError, retained: container.contains(player), style: player.getAttribute('style') === style, active: art.fullscreenWeb }
art.destroy()
return result
})).toEqual({ sameError: true, retained: true, style: true, active: false })
})
test('candidate: a failed web fullscreen exit can retry restoring the original placement', async ({ page }) => {
await setup(page, 'candidate')
expect(await page.evaluate(() => {
const art = window.art
const { $player: player, $container: container } = art.template
art.constructor.FULLSCREEN_WEB_IN_BODY = true
const style = player.getAttribute('style')
art.fullscreenWeb = true
const insert = container.insertBefore
const failure = new Error('controlled restore failure')
container.insertBefore = () => {
throw failure
}
let sameError = false
try {
art.fullscreenWeb = false
}
catch (error) {
sameError = error === failure
}
container.insertBefore = insert
art.fullscreenWeb = false
const result = { sameError, retained: container.contains(player), style: player.getAttribute('style') === style, active: art.fullscreenWeb }
art.destroy()
return result
})).toEqual({ sameError: true, retained: true, style: true, active: false })
})
test('candidate: reentry during web fullscreen restoration preserves the newer active mode', async ({ page }) => {
await setup(page, 'candidate')
expect(await page.evaluate(() => {
const art = window.art
const { $player: player, $container: container } = art.template
art.constructor.FULLSCREEN_WEB_IN_BODY = true
player.style.width = '65%'
const style = player.getAttribute('style')
art.fullscreenWeb = true
const insert = container.insertBefore
container.insertBefore = function (node, anchor) {
container.insertBefore = insert
const result = insert.call(this, node, anchor)
art.fullscreenWeb = true
return result
}
art.fullscreenWeb = false
const winner = art.fullscreenWeb && player.parentNode === document.body && player.style.width === '100%'
art.fullscreenWeb = false
const restored = container.contains(player) && player.getAttribute('style') === style
art.destroy()
return { winner, restored }
})).toEqual({ winner: true, restored: true })
})
for (const core of ['published', 'candidate']) {
for (const initial of ['', 'width: 70%; height: 80%; color: red;']) {
test(`${core}: web fullscreen restores initial style after repeated entry (${initial || 'empty'})`, async ({ page }, testInfo) => {
await setup(page, core)
const result = await page.evaluate((initial) => {
const art = window.art
art.constructor.FULLSCREEN_WEB_IN_BODY = false
const player = art.template.$player
player.style.cssText = initial
const before = player.style.cssText
const events = []
let restoredAtExit
art.on('fullscreenWeb', (value) => {
events.push(value)
if (!value)
restoredAtExit = player.style.cssText
})
art.fullscreenWeb = true
art.fullscreenWeb = true
art.fullscreenWeb = false
const result = { restored: restoredAtExit === before, events, active: art.fullscreenWeb, dimensions: [player.style.width, player.style.height, player.style.color], styles: { before, restoredAtExit, after: player.style.cssText } }
art.destroy()
return result
}, initial)
const { styles, ...observed } = result
await testInfo.attach('fullscreen-styles', { contentType: 'application/json', body: JSON.stringify(styles) })
expect(observed).toEqual({ restored: core === 'candidate', events: [true, true, false], active: false, dimensions: core === 'candidate' ? (initial ? ['70%', '80%', 'red'] : ['', '', '']) : ['100%', '100%', initial ? 'red' : ''] })
})
}
for (const changeFlag of [false, true]) {
test(`${core}: body fullscreen restores original parent and sibling when flag changes=${changeFlag}`, async ({ page }) => {
await setup(page, core)
const result = await page.evaluate((changeFlag) => {
const art = window.art
const player = art.template.$player
const wrapper = document.createElement('div')
const sibling = document.createElement('span')
art.template.$container.append(wrapper)
wrapper.append(player, sibling)
art.constructor.FULLSCREEN_WEB_IN_BODY = true
art.fullscreenWeb = true
const entered = player.parentNode === document.body
if (changeFlag)
art.constructor.FULLSCREEN_WEB_IN_BODY = false
art.fullscreenWeb = false
const result = { entered, parent: player.parentNode === wrapper, sibling: player.nextSibling === sibling, inBody: player.parentNode === document.body }
art.destroy()
player.remove()
return result
}, changeFlag)
expect(result).toEqual({ entered: true, parent: core === 'candidate', sibling: core === 'candidate', inBody: core === 'published' && changeFlag })
})
}
for (const removeHtml of [false, true]) {
test(`${core}: destroy while web fullscreen removeHtml=${removeHtml} restores DOM ownership`, async ({ page }) => {
await setup(page, core)
const result = await page.evaluate((removeHtml) => {
const art = window.art
const { $player: player, $container: container } = art.template
const style = player.style.cssText
art.constructor.FULLSCREEN_WEB_IN_BODY = true
art.fullscreenWeb = true
art.destroy(removeHtml)
const result = { orphan: player.parentNode === document.body, retained: container.contains(player), style: player.style.cssText === style, fullscreen: player.classList.contains('art-fullscreen-web') }
player.remove()
return result
}, removeHtml)
expect(result).toEqual(core === 'candidate'
? { orphan: false, retained: !removeHtml, style: true, fullscreen: false }
: { orphan: true, retained: false, style: false, fullscreen: true })
})
}
}