Files
ArtPlayer/test/browser/multiple-subtitles-history.spec.js

137 lines
7.4 KiB
JavaScript

import { hash } from '../../refactor/scripts/releases.mjs'
import { multipleSubtitlesHistorical } from '../helpers/multiple-subtitles.js'
import { expect, test } from './fixtures.js'
const implementation = (await multipleSubtitlesHistorical()).find(item => item.name === 'published-1.2.0-main')
const srt = text => `1\n00:00:00,000 --> 00:00:08,000\n${text}\n`
const vtt = 'WEBVTT\n\n00:00.000 --> 00:08.000\nCached caption\n'
async function prepare(page, core) {
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: implementation.code })
await page.evaluate(() => {
window.createPlayer('/test/pattern.mp4')
window.multipleProbe = { initialized: [], revoked: [], errors: [], signal: false, settled: false }
window.multipleNativeFetch = window.fetch.bind(window)
window.fetch = (url, options) => {
if (url === '/test/multiple-held.vtt')
window.multipleProbe.signal = Boolean(options?.signal)
return window.multipleNativeFetch(url, options)
}
const revoke = URL.revokeObjectURL.bind(URL)
URL.revokeObjectURL = (url) => {
window.multipleProbe.revoked.push(url)
revoke(url)
}
const init = window.art.subtitle.init.bind(window.art.subtitle)
window.art.subtitle.init = (option) => {
window.multipleProbe.initialized.push({ url: option.url, destroyed: window.art.isDestroy })
const pending = init(option)
// Observe the real host rejection without replacing its behavior or return value.
Promise.resolve(pending).catch(error => window.multipleProbe.errors.push({ name: error.name, message: error.message }))
return pending
}
})
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
}
async function evidence(page, testInfo, core, observation) {
const probe = await page.evaluate(() => window.multipleProbe)
expect(probe.errors).toEqual([])
await testInfo.attach('multiple-subtitles-history', { body: JSON.stringify({ core, implementation: { name: implementation.name, sha256: hash(implementation.code) }, observation, probe }), contentType: 'application/json' })
}
for (const core of ['published', 'candidate']) {
test(`Multiple subtitles historical ${core} core: real SRT captions select, clear and reset`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-en.srt', route => route.fulfill({ body: srt('English'), contentType: 'text/plain' }))
await page.route('**/test/multiple-jp.srt', route => route.fulfill({ body: srt('日本語'), contentType: 'text/plain' }))
await prepare(page, core)
await page.evaluate(async () => {
window.multipleResult = await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ name: 'en', url: '/test/multiple-en.srt' }, { name: 'jp', url: '/test/multiple-jp.srt' }] }))
})
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(2)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
await page.locator('#pause').click()
await expect(page.locator('.art-subtitle-en')).toHaveText('English')
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks(['jp']))
await expect(page.locator('.art-subtitle-en')).toHaveCount(0)
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks([]))
await expect(page.locator('.art-subtitle-jp')).toHaveCount(0)
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
await expect(page.locator('.art-subtitle-en')).toHaveText('English')
await expect(page.locator('.art-subtitle-jp')).toHaveText('日本語')
await page.evaluate(() => window.art.destroy(false))
await evidence(page, testInfo, core, 'Native SRT conversion, track cue loading, real playback and DOM text selection/reset passed; this does not verify all CSS/device combinations.')
})
test(`Multiple subtitles historical ${core} core: pending native fetch survives destroy and creates a retained Blob URL`, async ({ page }, testInfo) => {
let release
let requested
const started = new Promise((resolve) => {
requested = resolve
})
const pending = new Promise((resolve) => {
release = resolve
})
await page.route('**/test/multiple-held.vtt', async (route) => {
requested()
await pending
await route.fulfill({ body: vtt, contentType: 'text/vtt' })
})
try {
await prepare(page, core)
await page.evaluate(() => {
window.multiplePending = window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-held.vtt', name: 'held' }] })(window.art).then((result) => {
window.multipleResult = result
window.multipleProbe.settled = true
})
})
await started
await page.evaluate(() => window.art.destroy(false))
expect(await page.evaluate(() => ({ settled: window.multipleProbe.settled, signal: window.multipleProbe.signal, initialized: window.multipleProbe.initialized.length }))).toEqual({ settled: false, signal: false, initialized: 0 })
release()
await page.evaluate(() => window.multiplePending)
const state = await page.evaluate(async () => {
const [late] = window.multipleProbe.initialized
const text = await (await window.multipleNativeFetch(late.url)).text()
return { destroyedAtInit: late.destroyed, retained: !window.multipleProbe.revoked.includes(late.url), text }
})
expect(state.destroyedAtInit).toBe(true)
expect(state.retained).toBe(true)
expect(state.text).toContain('Cached caption')
await evidence(page, testInfo, core, state)
}
finally { release() }
})
test(`Multiple subtitles historical ${core} core: destroy retains the plugin URL and reset allocates another one`, async ({ page }, testInfo) => {
await page.route('**/test/multiple-ready.vtt', route => route.fulfill({ body: vtt, contentType: 'text/vtt' }))
await prepare(page, core)
await page.evaluate(async () => {
window.multipleResult = await window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-ready.vtt', name: 'ready' }] })(window.art)
})
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
const state = await page.evaluate(async () => {
const first = window.multipleProbe.initialized[0].url
window.art.destroy(false)
const retainedText = await (await window.multipleNativeFetch(first)).text()
const beforeReset = !window.multipleProbe.revoked.includes(first)
const result = window.multipleResult.reset()
const late = window.multipleProbe.initialized.at(-1)
const lateText = await (await window.multipleNativeFetch(late.url)).text()
return { beforeReset, retainedText, returnsUndefined: result === undefined, lateAfterDestroy: late.destroyed, newURL: late.url !== first, priorRevokedByReset: window.multipleProbe.revoked.includes(first), lateText }
})
expect(state.beforeReset).toBe(true)
expect(state.retainedText).toContain('Cached caption')
expect(state.returnsUndefined).toBe(true)
expect(state.lateAfterDestroy).toBe(true)
expect(state.newURL).toBe(true)
expect(state.priorRevokedByReset).toBe(true)
expect(state.lateText).toContain('Cached caption')
await evidence(page, testInfo, core, state)
})
}