mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 04:01:20 -08:00
refactor(core): CORE-13 type component registries and own control lifetimes
This commit is contained in:
1 parent
9eb7c945ca
commit
697817eceb
68 files changed
+10100
-1925
No files matched your search
@@ -0,0 +1,378 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (const fallback of [false, true]) {
|
||||
test(`candidate: control layout follows resize and removal with observer fallback=${fallback}`, async ({ page }) => {
|
||||
if (fallback) {
|
||||
await page.addInitScript(() => {
|
||||
window.ResizeObserver = undefined
|
||||
})
|
||||
}
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.evaluate(() => {
|
||||
window.art = new window.Artplayer({ container: '.player', url: '', setting: true, fullscreenWeb: true, controls: [{ name: 'probe', position: 'right', html: 'Probe control' }] })
|
||||
})
|
||||
const heights = () => page.evaluate(() => ({ actual: window.art.template.$controls.offsetHeight, recorded: Number.parseFloat(window.art.template.$player.style.getPropertyValue('--art-controls-height')) }))
|
||||
await expect.poll(heights).toEqual({ actual: 46, recorded: 46 })
|
||||
await page.evaluate(() => {
|
||||
window.art.template.$container.style.width = '320px'
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
await expect.poll(heights).toEqual({ actual: 92, recorded: 92 })
|
||||
await expect.poll(() => page.evaluate(() => Number.parseFloat(getComputedStyle(window.art.template.$subtitle).bottom))).toBeGreaterThanOrEqual(92)
|
||||
await page.evaluate(() => window.art.controls.remove('probe'))
|
||||
await expect.poll(heights).toEqual({ actual: 46, recorded: 46 })
|
||||
await page.evaluate(() => {
|
||||
window.art.destroy(false)
|
||||
window.art.template.$controls.style.display = 'none'
|
||||
window.art.emit('resize')
|
||||
})
|
||||
await expect.poll(heights).toEqual({ actual: 0, recorded: 46 })
|
||||
})
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: selector background clicks and rejection ownership are explicit`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => {
|
||||
const failure = new Error('selection failure')
|
||||
window.selectionReports = { warnings: [], rejections: [] }
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
event.preventDefault()
|
||||
window.selectionReports.rejections.push({ name: event.reason.name, same: event.reason === failure })
|
||||
})
|
||||
const original = console.warn
|
||||
console.warn = (message, error) => {
|
||||
window.selectionReports.warnings.push({ message, same: error === failure })
|
||||
original.call(console, message, error)
|
||||
}
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
window.art = art
|
||||
art.controls.add({ name: 'selector', position: 'right', html: 'initial', selector: [{ html: 'A' }], onSelect() {
|
||||
return Promise.reject(failure)
|
||||
} })
|
||||
art.controls.selector.querySelector('.art-selector-list').dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
if (core === 'published')
|
||||
await expect.poll(() => page.evaluate(() => window.selectionReports.rejections.length)).toBe(1)
|
||||
await page.evaluate(() => window.art.controls.selector.querySelector('.art-selector-item').dispatchEvent(new MouseEvent('click', { bubbles: true })))
|
||||
await expect.poll(() => page.evaluate(() => window.selectionReports.warnings.length + window.selectionReports.rejections.length)).toBe(core === 'published' ? 2 : 1)
|
||||
const reports = await page.evaluate(() => window.selectionReports)
|
||||
expect(reports).toEqual(core === 'published'
|
||||
? { warnings: [], rejections: [{ name: 'TypeError', same: false }, { name: 'Error', same: true }] }
|
||||
: { warnings: [{ message: 'ArtPlayer selector failed:', same: true }], rejections: [] })
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
|
||||
test(`${core}: beforeUnmount errors remain retryable and detached nodes can be removed`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const failure = new Error('before unmount')
|
||||
const option = { name: 'entry', html: 'entry', beforeUnmount() {
|
||||
throw failure
|
||||
} }
|
||||
const element = art.layers.add(option)
|
||||
let same = false
|
||||
try {
|
||||
art.layers.remove('entry')
|
||||
}
|
||||
catch (error) { same = error === failure }
|
||||
const retained = art.layers.cache.has('entry') && element.isConnected
|
||||
option.beforeUnmount = () => {}
|
||||
element.remove()
|
||||
let error = null
|
||||
try {
|
||||
art.layers.remove('entry')
|
||||
}
|
||||
catch (caught) { error = caught.name }
|
||||
const result = { same, retained, error, removed: !art.layers.cache.has('entry') }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ same: true, retained: true, error: core === 'published' ? 'TypeError' : null, removed: true })
|
||||
})
|
||||
|
||||
test(`${core}: failed mounted reentry preserves its successful replacement`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const failure = new Error('outer failure')
|
||||
let same = false
|
||||
try {
|
||||
art.layers.add({ name: 'entry', html: 'outer', mounted() {
|
||||
art.layers.remove('entry')
|
||||
art.layers.add({ name: 'entry', html: 'replacement' })
|
||||
throw failure
|
||||
} })
|
||||
}
|
||||
catch (error) { same = error === failure }
|
||||
const result = { same, value: art.layers.entry.textContent, attached: art.layers.entry.isConnected, entries: art.layers.cache.size }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ same: true, value: 'replacement', attached: true, entries: 1 })
|
||||
})
|
||||
|
||||
test(`${core}: context menu builtin selection and owned updates preserve state`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => {
|
||||
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', playbackRate: true, aspectRatio: true, flip: true })
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
for (const [name, value] of [['playbackRate', '1.5'], ['aspectRatio', '4:3'], ['flip', 'horizontal']]) {
|
||||
await page.locator('.art-video-player').click({ button: 'right', position: { x: 100, y: 80 } })
|
||||
await page.locator(`.art-contextmenu-${name} [data-value="${value}"]`).click()
|
||||
await expect.poll(() => page.evaluate(name => window.art.contextmenu[name].querySelector('.art-current')?.dataset.value, name)).toBe(value)
|
||||
expect(await page.evaluate(() => window.art.contextmenu.show)).toBe(false)
|
||||
}
|
||||
const result = await page.evaluate(() => {
|
||||
const { art } = window
|
||||
const menu = art.contextmenu.flip
|
||||
art.contextmenu.remove('flip')
|
||||
art.flip = 'normal'
|
||||
const result = { rate: art.playbackRate, aspect: art.aspectRatio, changedAfterRemove: menu.querySelector('[data-value="normal"]').classList.contains('art-current') }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ rate: 1.5, aspect: '4:3', changedAfterRemove: core === 'published' })
|
||||
})
|
||||
|
||||
test(`${core}: a prototype-named component keeps the registry prototype intact`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const prototype = Object.getPrototypeOf(art.layers)
|
||||
const element = art.layers.add({ name: '__proto__', html: 'named' })
|
||||
const alias = Reflect.get(art.layers, '__proto__') === element
|
||||
art.layers.remove('__proto__')
|
||||
const result = { alias, preserved: Object.getPrototypeOf(art.layers) === prototype }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ alias: true, preserved: core === 'candidate' })
|
||||
})
|
||||
|
||||
test(`${core}: component factories, ordering, bound methods and update hooks retain contracts`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const { add, update, remove } = art.layers
|
||||
const calls = []
|
||||
const first = { name: 'first', index: 10, html: 'first', mounted(element) {
|
||||
calls.push(['mount', this === art, element.textContent])
|
||||
} }
|
||||
add(first)
|
||||
add({ name: 'equal', index: 10, html: 'equal' })
|
||||
let factory
|
||||
add((owner) => {
|
||||
factory = owner === art
|
||||
return { name: 'zero', index: 0, html: 42 }
|
||||
})
|
||||
const disabled = { html: 0, disable: true }
|
||||
const before = art.layers.id
|
||||
const disabledResult = add(disabled)
|
||||
const order = Array.from(art.template.$layer.children, node => node.textContent)
|
||||
const updated = update({ name: 'first', html: 'changed', beforeUnmount(element) {
|
||||
calls.push(['unmount', this === art, element.textContent, first.html])
|
||||
} })
|
||||
const controlResult = art.controls.add({ name: 'probe', position: 'right', html: 'probe', click(component, event) {
|
||||
calls.push(['click', this === art, component === art.controls, event.defaultPrevented])
|
||||
} })
|
||||
art.controls.probe.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
||||
const controlUpdate = art.controls.update({ name: 'probe', html: 'updated' })
|
||||
const result = { factory, order, disabled: disabledResult === undefined && art.layers.id === before + 1 && disabled.html === '', updated: updated === art.layers.first, controlReturns: [controlResult === undefined, controlUpdate === undefined], calls }
|
||||
remove('first')
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result.factory).toBe(true)
|
||||
expect(result.order).toEqual(['42', 'equal', 'first'])
|
||||
expect(result.disabled).toBe(true)
|
||||
expect(result.updated).toBe(true)
|
||||
expect(result.controlReturns).toEqual([true, true])
|
||||
expect(result.calls.slice(0, 4)).toEqual([['mount', true, 'first'], ['unmount', true, 'first', 'changed'], ['mount', true, 'changed'], ['click', true, true, true]])
|
||||
})
|
||||
|
||||
test(`${core}: removed builtin time and volume controls stop owned effects`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const time = art.controls.time
|
||||
const before = time.textContent
|
||||
const icon = art.controls.volume.querySelector('.art-icon-volume')
|
||||
art.controls.remove('time')
|
||||
art.controls.remove('volume')
|
||||
art.video.currentTime = 12
|
||||
art.emit('video:timeupdate', new Event('timeupdate'))
|
||||
art.muted = false
|
||||
icon.dispatchEvent(new MouseEvent('click'))
|
||||
const result = { changed: time.textContent !== before, muted: art.muted }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ changed: core === 'published', muted: core === 'published' })
|
||||
})
|
||||
|
||||
test(`${core}: selector items can be reused by an update without changing getter flags`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const selector = [{ html: 'A', value: 'a', default: true }, { html: 'B', value: 'b' }]
|
||||
art.controls.add({ name: 'selector', position: 'right', html: 'A', selector })
|
||||
const oldNode = selector[0].$control_item
|
||||
let error = null
|
||||
try {
|
||||
art.controls.update({ name: 'selector', html: 'updated' })
|
||||
}
|
||||
catch (caught) { error = caught.name }
|
||||
const descriptor = Object.getOwnPropertyDescriptor(selector[0], '$control_item')
|
||||
const result = { error, changed: selector[0].$control_item !== oldNode, flags: [descriptor.enumerable, descriptor.configurable, typeof descriptor.get], options: selector[0].$control_option === selector }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ error: core === 'candidate' ? null : 'TypeError', changed: core === 'candidate', flags: [false, false, 'function'], options: true })
|
||||
})
|
||||
|
||||
test(`${core}: async selector results cannot overwrite a newer choice or removed entry`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(async () => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const selector = [{ html: 'A', value: 'a' }, { html: 'B', value: 'b' }]
|
||||
const pending = []
|
||||
const calls = []
|
||||
art.controls.add({ name: 'selector', position: 'right', html: 'initial', selector, onSelect(item, node, event) {
|
||||
calls.push([this === art, selector.includes(item), item.$control_item === node, event.type])
|
||||
return new Promise(resolve => pending.push(resolve))
|
||||
} })
|
||||
const click = item => item.$control_item.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
click(selector[0])
|
||||
click(selector[1])
|
||||
pending[1]('new')
|
||||
await Promise.resolve()
|
||||
pending[0]('old')
|
||||
await Promise.resolve()
|
||||
const value = selector[0].$control_value
|
||||
const raced = value.innerHTML
|
||||
click(selector[0])
|
||||
const beforeRemove = value.innerHTML
|
||||
art.controls.remove('selector')
|
||||
pending[2]('removed')
|
||||
await Promise.resolve()
|
||||
const result = { raced, changedAfterRemove: value.innerHTML !== beforeRemove, flags: selector.map(item => item.default), calls }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result.raced).toBe(core === 'candidate' ? 'new' : 'old')
|
||||
expect(result.changedAfterRemove).toBe(core === 'published')
|
||||
expect(result.flags).toEqual([true, false])
|
||||
expect(result.calls).toEqual(Array.from({ length: 3 }, () => [true, true, true, 'click']))
|
||||
})
|
||||
|
||||
test(`${core}: a failed mounted hook rolls back its DOM alias and listeners`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
const failure = new Error('mounted failure')
|
||||
let element
|
||||
let sameError = false
|
||||
let clicks = 0
|
||||
try {
|
||||
art.layers.add({ name: 'failed', html: 'failed', click() {
|
||||
clicks++
|
||||
}, mounted(node) {
|
||||
element = node
|
||||
throw failure
|
||||
} })
|
||||
}
|
||||
catch (error) { sameError = error === failure }
|
||||
element.dispatchEvent(new MouseEvent('click'))
|
||||
const result = { sameError, clicks, cached: art.layers.cache.has('failed'), alias: art.layers.failed === element, attached: art.template.$layer.contains(element) }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ sameError: true, clicks: core === 'candidate' ? 0 : 1, cached: core === 'published', alias: core === 'published', attached: core === 'published' })
|
||||
})
|
||||
|
||||
test(`${core}: reentrant beforeUnmount removes only once`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '' })
|
||||
let calls = 0
|
||||
art.layers.add({ name: 'reentrant', html: 'reentrant', beforeUnmount() {
|
||||
calls++
|
||||
if (calls === 1)
|
||||
art.layers.remove('reentrant')
|
||||
} })
|
||||
let error = null
|
||||
try {
|
||||
art.layers.remove('reentrant')
|
||||
}
|
||||
catch (caught) { error = caught.name }
|
||||
const result = { calls, error, cached: art.layers.cache.has('reentrant') }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ calls: core === 'candidate' ? 1 : 2, error: core === 'candidate' ? null : 'TypeError', cached: false })
|
||||
})
|
||||
|
||||
test(`${core}: narrow controls stay inside the player without hiding buttons`, async ({ page }, testInfo) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = new window.Artplayer({ container: '.player', url: '', setting: true, fullscreenWeb: true, controls: [{ name: 'probe', position: 'right', html: 'Probe control' }] })
|
||||
const container = art.template.$container
|
||||
const measure = (width) => {
|
||||
container.style.width = `${width}px`
|
||||
container.style.height = `${width * 9 / 16}px`
|
||||
const bounds = container.getBoundingClientRect()
|
||||
const controls = Array.from(container.querySelectorAll('.art-controls .art-control'))
|
||||
return { count: controls.length, hidden: controls.filter(node => node.getClientRects().length === 0).length, overflow: controls.filter((node) => {
|
||||
const box = node.getBoundingClientRect()
|
||||
return box.left < bounds.left - 0.75 || box.right > bounds.right + 0.75 || box.top < bounds.top - 0.75 || box.bottom > bounds.bottom + 0.75
|
||||
}).length }
|
||||
}
|
||||
const wide = measure(640)
|
||||
const narrow = measure(320)
|
||||
const smaller = measure(240)
|
||||
measure(320)
|
||||
window.layoutArt = art
|
||||
return { wide, narrow, smaller }
|
||||
})
|
||||
await page.locator('.player').screenshot({ path: testInfo.outputPath('narrow-controls.png') })
|
||||
await page.evaluate(() => window.layoutArt.destroy())
|
||||
expect(result.wide.overflow).toBe(0)
|
||||
expect(result.narrow.count).toBe(result.wide.count)
|
||||
expect(result.narrow.hidden).toBe(0)
|
||||
expect(result.smaller.hidden).toBe(0)
|
||||
if (core === 'candidate') {
|
||||
expect(result.narrow.overflow).toBe(0)
|
||||
expect(result.smaller.overflow).toBe(0)
|
||||
}
|
||||
else {
|
||||
expect(result.narrow.overflow).toBeGreaterThan(0)
|
||||
expect(result.smaller.overflow).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
test(`${core}: progress markers treat text as data and removed drags cannot seek`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => {
|
||||
window.createPlayer('/test/pattern.mp4')
|
||||
window.art.option.highlight = [{ time: 1, text: 'label" data-injected="yes' }]
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
const result = await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const marker = art.controls.progress.querySelector('.art-progress-highlight span')
|
||||
const text = marker.dataset.text
|
||||
const injected = marker.hasAttribute('data-injected')
|
||||
let seeks = 0
|
||||
art.on('seek', () => seeks++)
|
||||
art.template.$progress.dispatchEvent(new MouseEvent('mousedown', { button: 0 }))
|
||||
art.controls.remove('progress')
|
||||
art.emit('document:mousemove', new MouseEvent('mousemove', { clientX: 100 }))
|
||||
art.destroy()
|
||||
return { text, injected, seeks }
|
||||
})
|
||||
expect(result).toEqual({ text: core === 'candidate' ? 'label" data-injected="yes' : 'label', injected: core === 'published', seeks: core === 'candidate' ? 0 : 1 })
|
||||
})
|
||||
}
|
||||
@@ -56,11 +56,32 @@ for (const core of ['published', 'candidate']) {
|
||||
})
|
||||
}
|
||||
|
||||
test('candidate: quality switch restores position/rate and preserves real customType identities', async ({ page }) => {
|
||||
test('candidate: quality switch restores position/rate and preserves real customType identities', async ({ page }, testInfo) => {
|
||||
await page.goto('/test/player.html?core=candidate&chapter=published')
|
||||
await page.evaluate(() => window.createPlayer('/test/pattern.mp4?custom=initial'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
// A real gesture starts WebKit's media pipeline before the paused seek setup.
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.locator('#pause').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.video.paused)).toBe(true)
|
||||
await testInfo.attach('initial-media-ranges', {
|
||||
body: JSON.stringify(await page.evaluate(() => {
|
||||
const { video } = window.art
|
||||
const ranges = value => Array.from({ length: value.length }, (_, index) => [value.start(index), value.end(index)])
|
||||
return { buffered: ranges(video.buffered), seekable: ranges(video.seekable), readyState: video.readyState, duration: video.duration, currentTime: video.currentTime }
|
||||
})),
|
||||
contentType: 'application/json',
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => {
|
||||
const ranges = window.art.video.seekable
|
||||
return Array.from({ length: ranges.length }, (_, index) => index).some(index => ranges.start(index) <= 3 && ranges.end(index) >= 3)
|
||||
})).toBe(true)
|
||||
await page.evaluate(() => {
|
||||
window.switchMediaEvents = []
|
||||
for (const name of ['loadedmetadata', 'seeking', 'seeked', 'canplay', 'pause', 'playing']) {
|
||||
window.art.video.addEventListener(name, () => window.switchMediaEvents.push({ name, time: window.art.currentTime, seeking: window.art.video.seeking, source: window.art.video.currentSrc }))
|
||||
}
|
||||
window.art.currentTime = 3
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.video.seeking)).toBe(false)
|
||||
@@ -76,11 +97,16 @@ test('candidate: quality switch restores position/rate and preserves real custom
|
||||
art.aspectRatio = '16:9'
|
||||
const restart = []
|
||||
art.on('restart', url => restart.push(url))
|
||||
const beforeSwitch = { time: art.currentTime, seeking: art.video.seeking, paused: art.video.paused }
|
||||
await art.switchQuality('/test/pattern.mp4?custom=next')
|
||||
const result = { calls, restart, time: art.currentTime, rate: art.playbackRate, ratio: art.aspectRatio, paused: art.video.paused, error: art.video.error?.code || 0 }
|
||||
const result = { beforeSwitch, mediaEvents: window.switchMediaEvents, calls, restart, time: art.currentTime, rate: art.playbackRate, ratio: art.aspectRatio, paused: art.video.paused, error: art.video.error?.code || 0 }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
await testInfo.attach('quality-switch-media-state', { body: JSON.stringify(result), contentType: 'application/json' })
|
||||
expect(result.beforeSwitch.time).toBeCloseTo(3, 1)
|
||||
expect(result.beforeSwitch.seeking).toBe(false)
|
||||
expect(result.beforeSwitch.paused).toBe(true)
|
||||
expect(result.calls).toEqual([{ thisIdentity: true, ownerIdentity: true, videoIdentity: true, count: 3, url: '/test/pattern.mp4?custom=next' }])
|
||||
expect(result.restart).toEqual(['/test/pattern.mp4?custom=next'])
|
||||
expect(result.time).toBeCloseTo(3, 1)
|
||||
|
||||
Reference in new issue
Block a user