mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 12:11:20 -08:00
refactor(core): CORE-16 type display modes and own fullscreen lifetimes
This commit is contained in:
1 parent
1b83081e8f
commit
88aef830df
81 files changed
+33897
-1503
No files matched your search
@@ -0,0 +1,285 @@
|
||||
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')
|
||||
window.miniEvents = []
|
||||
window.art.on('mini', value => window.miniEvents.push(value))
|
||||
const video = window.art.template.$video
|
||||
const parent = document.createElement('div')
|
||||
parent.id = 'original-video-parent'
|
||||
const next = document.createElement('span')
|
||||
next.id = 'original-video-next'
|
||||
video.before(parent)
|
||||
parent.append(video, next)
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: mini retains first-entry and reuse DOM order and display`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
art.mini = true
|
||||
const popup = art.template.$mini
|
||||
const first = popup.firstChild === art.template.$video
|
||||
const display = popup.style.display
|
||||
art.mini = true
|
||||
const result = { first, display, last: popup.lastChild === art.template.$video, reusedDisplay: popup.style.display }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ first: true, display: '', last: true, reusedDisplay: 'flex' })
|
||||
})
|
||||
|
||||
test(`${core}: mini restores the original video parent and sibling after repeated entry`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
art.mini = true
|
||||
const popup = art.template.$mini
|
||||
art.mini = true
|
||||
art.mini = false
|
||||
const result = { restored: art.template.$video.parentElement.id === 'original-video-parent', next: art.template.$video.nextSibling?.id === 'original-video-next', reused: popup === art.template.$mini, hidden: popup.style.display, events: [...window.miniEvents] }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ restored: core === 'candidate', next: core === 'candidate', reused: true, hidden: 'none', events: [true, true, false] })
|
||||
})
|
||||
|
||||
for (const removeHtml of [false, true]) {
|
||||
test(`${core}: destroying active mini removeHtml=${removeHtml} removes the owned popup`, async ({ page }) => {
|
||||
await setup(page, core)
|
||||
expect(await page.evaluate((removeHtml) => {
|
||||
const art = window.art
|
||||
art.mini = true
|
||||
const popup = art.template.$mini
|
||||
const video = art.template.$video
|
||||
window.miniEvents.length = 0
|
||||
art.destroy(removeHtml)
|
||||
return { popupConnected: popup.isConnected, videoInPopup: popup.contains(video), events: window.miniEvents }
|
||||
}, removeHtml)).toEqual({ popupConnected: core === 'published', videoInPopup: core === 'published', events: [] })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test('candidate: hiding mini cancels drag and later document events cannot persist a hidden popup', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
const result = await page.evaluate(() => {
|
||||
const art = window.art
|
||||
art.mini = true
|
||||
const popup = art.template.$mini
|
||||
const original = { left: art.storage.get('left'), top: art.storage.get('top') }
|
||||
popup.dispatchEvent(new MouseEvent('mousedown', { button: 0, clientX: 20, clientY: 20 }))
|
||||
art.emit('document:mousemove', new MouseEvent('mousemove', { clientX: 40, clientY: 50 }))
|
||||
art.mini = false
|
||||
art.emit('document:mouseup', new MouseEvent('mouseup'))
|
||||
const result = { original, after: { left: art.storage.get('left'), top: art.storage.get('top') }, dragging: popup.classList.contains('art-mini-dragging'), transform: popup.style.transform }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result.after).toEqual(result.original)
|
||||
expect(result.dragging).toBe(false)
|
||||
expect(result.transform).toBe('')
|
||||
})
|
||||
|
||||
test('candidate: mini remains in the viewport on small screens', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 280, height: 160 })
|
||||
await setup(page, 'candidate')
|
||||
await page.evaluate(() => {
|
||||
window.art.storage.set('left', 9000)
|
||||
window.art.storage.set('top', 9000)
|
||||
window.art.mini = true
|
||||
})
|
||||
const rect = await page.locator('.art-mini-popup').boundingBox()
|
||||
expect(rect.x).toBeGreaterThanOrEqual(0)
|
||||
expect(rect.y).toBeGreaterThanOrEqual(0)
|
||||
expect(rect.x + rect.width).toBeLessThanOrEqual(280)
|
||||
expect(rect.y + rect.height).toBeLessThanOrEqual(160)
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
|
||||
test('candidate: mini drag stores the actual position and playback buttons remain usable', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
await page.evaluate(() => window.art.mini = true)
|
||||
const popup = page.locator('.art-mini-popup')
|
||||
const rect = await popup.boundingBox()
|
||||
await page.mouse.move(rect.x + 30, rect.y + 20)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(rect.x - 30, rect.y - 20)
|
||||
await page.mouse.up()
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const rect = art.template.$mini.getBoundingClientRect()
|
||||
return Math.abs(art.storage.get('left') - rect.left) < 1 && Math.abs(art.storage.get('top') - rect.top) < 1
|
||||
})).toBe(true)
|
||||
await popup.locator('.art-icon-play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.playing && window.art.currentTime > 0)).toBe(true)
|
||||
await popup.locator('.art-icon-pause').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
await popup.locator('.art-mini-close').click()
|
||||
await expect(popup).toBeHidden()
|
||||
expect(await page.evaluate(() => window.art.template.$video.parentElement.id)).toBe('original-video-parent')
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
|
||||
test('candidate: mini exit failure keeps the active video and can restore on retry', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const parent = art.template.$video.parentNode
|
||||
art.mini = true
|
||||
const insert = parent.insertBefore
|
||||
const failure = new Error('controlled mini restore failure')
|
||||
parent.insertBefore = () => {
|
||||
throw failure
|
||||
}
|
||||
let rejected = false
|
||||
try {
|
||||
art.mini = false
|
||||
}
|
||||
catch (error) { rejected = error === failure }
|
||||
const active = art.mini && art.template.$mini.contains(art.template.$video)
|
||||
parent.insertBefore = insert
|
||||
art.mini = false
|
||||
const result = { rejected, active, restored: art.template.$video.parentNode === parent }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ rejected: true, active: true, restored: true })
|
||||
})
|
||||
|
||||
test('candidate: mini reentry during restoration retains the new placement snapshot', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const parent = art.template.$video.parentNode
|
||||
const insert = parent.insertBefore
|
||||
art.mini = true
|
||||
parent.insertBefore = function (...args) {
|
||||
const result = insert.apply(this, args)
|
||||
parent.insertBefore = insert
|
||||
art.mini = true
|
||||
return result
|
||||
}
|
||||
art.mini = false
|
||||
const active = art.mini
|
||||
art.mini = false
|
||||
const result = { active, restored: art.template.$video.parentNode === parent, events: [...window.miniEvents] }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ active: true, restored: true, events: [true, true, false] })
|
||||
})
|
||||
|
||||
for (const action of ['hide', 'destroy', 'fail']) {
|
||||
test(`candidate: mini construction ${action} releases incomplete UI and restores the video`, async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate((action) => {
|
||||
const art = window.art
|
||||
const icons = art.icons
|
||||
const failure = new Error('controlled mini icon failure')
|
||||
art.icons = {
|
||||
get close() {
|
||||
if (action === 'hide')
|
||||
art.mini = false
|
||||
else if (action === 'destroy')
|
||||
art.destroy(false)
|
||||
else
|
||||
throw failure
|
||||
return icons.close
|
||||
},
|
||||
get play() { return icons.play },
|
||||
get pause() { return icons.pause },
|
||||
}
|
||||
let rejected = false
|
||||
try {
|
||||
art.mini = true
|
||||
}
|
||||
catch (error) {
|
||||
rejected = error === failure
|
||||
}
|
||||
const restored = art.template.$video.parentElement.id === 'original-video-parent'
|
||||
const active = art.mini
|
||||
let retry = null
|
||||
if (action !== 'destroy') {
|
||||
art.icons = icons
|
||||
art.mini = true
|
||||
retry = art.mini && document.querySelectorAll('.art-mini-popup').length === 1
|
||||
art.destroy()
|
||||
}
|
||||
return { restored, active, rejected, retry, remaining: document.querySelectorAll('.art-mini-popup').length }
|
||||
}, action)).toEqual({ restored: true, active: false, rejected: action === 'fail', retry: action === 'destroy' ? null : true, remaining: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
test('candidate: mini preserves a caller-owned popup on destruction', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const popup = document.createElement('div')
|
||||
popup.style.cssText = 'display: grid; width: 320px; height: 180px;'
|
||||
document.body.append(popup)
|
||||
art.template.$mini = popup
|
||||
art.mini = true
|
||||
art.destroy(false)
|
||||
const result = { connected: popup.isConnected, display: popup.style.display, restored: art.template.$video.parentElement.id === 'original-video-parent', empty: popup.childNodes.length === 0 }
|
||||
popup.remove()
|
||||
return result
|
||||
})).toEqual({ connected: true, display: 'grid', restored: true, empty: true })
|
||||
})
|
||||
|
||||
test('candidate: reentrant mini creation allocates only one owned popup', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const icons = art.icons
|
||||
let reentered = false
|
||||
art.icons = {
|
||||
get close() {
|
||||
if (!reentered) {
|
||||
reentered = true
|
||||
art.mini = true
|
||||
}
|
||||
return icons.close
|
||||
},
|
||||
get play() { return icons.play },
|
||||
get pause() { return icons.pause },
|
||||
}
|
||||
art.mini = true
|
||||
const result = { popups: document.querySelectorAll('.art-mini-popup').length, ownVideo: art.template.$mini.contains(art.template.$video), events: [...window.miniEvents] }
|
||||
art.destroy()
|
||||
return result
|
||||
})).toEqual({ popups: 1, ownVideo: true, events: [true] })
|
||||
})
|
||||
|
||||
test('candidate: mini switches to body web fullscreen and restores the original video placement', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
window.Artplayer.FULLSCREEN_WEB_IN_BODY = true
|
||||
art.mini = true
|
||||
art.fullscreenWeb = true
|
||||
const switched = !art.mini && art.fullscreenWeb && art.template.$mini.style.display === 'none'
|
||||
art.fullscreenWeb = false
|
||||
const restored = art.template.$video.parentElement.id === 'original-video-parent'
|
||||
art.mini = true
|
||||
art.mini = false
|
||||
const events = [...window.miniEvents]
|
||||
art.destroy()
|
||||
return { switched, restored, events }
|
||||
})).toEqual({ switched: true, restored: true, events: [true, false, true, false] })
|
||||
})
|
||||
|
||||
test('candidate: destroying a mini drag removes its document subscriptions', async ({ page }) => {
|
||||
await setup(page, 'candidate')
|
||||
expect(await page.evaluate(() => {
|
||||
const art = window.art
|
||||
art.mini = true
|
||||
art.template.$mini.dispatchEvent(new MouseEvent('mousedown', { button: 0, clientX: 10, clientY: 10 }))
|
||||
art.destroy(false)
|
||||
let writes = 0
|
||||
art.storage.set = () => writes++
|
||||
art.emit('document:mousemove', new MouseEvent('mousemove', { clientX: 100, clientY: 100 }))
|
||||
art.emit('document:mouseup', new MouseEvent('mouseup'))
|
||||
return writes
|
||||
})).toBe(0)
|
||||
})
|
||||
Reference in new issue
Block a user