mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
137 lines
5.8 KiB
JavaScript
137 lines
5.8 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
// eslint-disable-next-line test/no-import-node-test -- Caption adapters use the repository Node runner.
|
|
import test from 'node:test'
|
|
import { parseHTML } from 'linkedom'
|
|
import { multipleSubtitlesCandidate, multipleSubtitlesEnvironment } from './helpers/multiple-subtitles.js'
|
|
|
|
const candidate = await multipleSubtitlesCandidate()
|
|
|
|
function host({ modern = false } = {}) {
|
|
const env = multipleSubtitlesEnvironment(candidate)
|
|
const { document } = parseHTML('<html><body><div id="subtitle"></div></body></html>')
|
|
const target = document.getElementById('subtitle')
|
|
let active = []
|
|
env.art.template = { $subtitle: target }
|
|
Object.defineProperty(env.art.subtitle, 'activeCue', {
|
|
get() { throw new Error('Capability checks must not read a cue') },
|
|
})
|
|
Object.defineProperty(env.art.subtitle, 'textTrack', { get: () => ({ activeCues: active }) })
|
|
if (modern)
|
|
Object.defineProperty(env.art.subtitle, 'activeCues', { get() { throw new Error('Modern rendering belongs to the core') } })
|
|
env.art.subtitle.update = () => {
|
|
target.innerHTML = active[0]?.text || ''
|
|
if (active.length)
|
|
env.emit('subtitleUpdate', active[0].text)
|
|
}
|
|
return {
|
|
...env,
|
|
target,
|
|
setActive(cues) { active = cues },
|
|
}
|
|
}
|
|
|
|
test('Multiple subtitles legacy caption view preserves cue order, event payload and original update', async () => {
|
|
const env = host()
|
|
const update = env.art.subtitle.update
|
|
const payloads = []
|
|
const views = []
|
|
env.art.on('subtitleUpdate', (value) => {
|
|
payloads.push(value)
|
|
views.push(['before', env.target.textContent])
|
|
})
|
|
await env.factory({ subtitles: [] })(env.art)
|
|
env.art.on('subtitleUpdate', () => views.push(['after', env.target.textContent]))
|
|
const cues = Object.freeze([
|
|
Object.freeze({ text: '<div class="a"><b>A & safe</b></div>', startTime: 1, endTime: 3 }),
|
|
Object.freeze({ text: '<div class="b">B</div>', startTime: 2, endTime: 4 }),
|
|
Object.freeze({ text: '<div class="c">C</div>', startTime: 2.25, endTime: 2.75 }),
|
|
])
|
|
env.setActive(cues)
|
|
for (let index = 0; index < 3; index++) {
|
|
update()
|
|
assert.equal(env.target.textContent, 'A & safeBC')
|
|
assert.deepEqual(Array.from(env.target.children, node => node.className), ['a', 'b', 'c'])
|
|
}
|
|
assert.equal(env.art.subtitle.update, update)
|
|
assert.deepEqual(payloads, [cues[0].text, cues[0].text, cues[0].text])
|
|
assert.deepEqual(views, Array.from({ length: 3 }, () => [['before', 'A & safe'], ['after', 'A & safeBC']]).flat())
|
|
assert.equal(env.listeners.get('subtitleAfterUpdate'), undefined)
|
|
env.setActive([cues[1]])
|
|
update()
|
|
assert.equal(env.target.innerHTML, cues[1].text)
|
|
env.setActive([])
|
|
update()
|
|
assert.equal(env.target.innerHTML, '')
|
|
assert.equal(payloads.length, 4)
|
|
env.emit('destroy')
|
|
})
|
|
|
|
test('Multiple subtitles legacy view respects later escape changes and blank CRLF lines', async () => {
|
|
const env = host()
|
|
await env.factory({ subtitles: [] })(env.art)
|
|
env.art.option.subtitle.escape = true
|
|
env.setActive([{ text: '<b>A & B</b>\r\n\r\nTail' }, { text: '<img src="bad" onerror="bad()">' }])
|
|
env.art.subtitle.update()
|
|
assert.equal(env.target.querySelectorAll('b, img').length, 0)
|
|
assert.deepEqual(Array.from(env.target.children, node => node.textContent), ['<b>A & B</b>', '', 'Tail', '<img src="bad" onerror="bad()">'])
|
|
assert(Array.from(env.target.children).every(node => node.className === 'art-subtitle-line'))
|
|
env.emit('destroy')
|
|
})
|
|
|
|
test('Multiple subtitles legacy view cleans timestamp markers for both single and overlapping cues', async () => {
|
|
const env = host()
|
|
await env.factory({ subtitles: [] })(env.art)
|
|
const cue = { text: '<div class="a">A<c.artplayer-multiple-subtitles-timestamp><00:02.000></c><b>B</b></div>' }
|
|
for (const cues of [[cue], [cue, { text: '<div>C</div>' }]]) {
|
|
env.setActive(cues)
|
|
env.art.subtitle.update()
|
|
assert.equal(env.target.querySelector('b').textContent, 'B')
|
|
assert.equal(env.target.textContent, cues.length === 1 ? 'AB' : 'ABC')
|
|
assert.equal(env.target.getElementsByTagName('c.artplayer-multiple-subtitles-timestamp').length, 0)
|
|
}
|
|
env.emit('destroy')
|
|
})
|
|
|
|
test('Multiple subtitles modern hosts retain their own view and never install the legacy listener', async () => {
|
|
const env = host({ modern: true })
|
|
await env.factory({ subtitles: [] })(env.art)
|
|
env.target.innerHTML = '<span>Core view</span>'
|
|
env.setActive([{ text: 'A' }, { text: 'B' }])
|
|
env.emit('subtitleUpdate', 'A')
|
|
env.emit('subtitleAfterUpdate', ['A', 'B'])
|
|
assert.equal(env.target.innerHTML, '<span>Core view</span>')
|
|
assert.equal(env.listeners.get('subtitleUpdate'), undefined)
|
|
assert.equal(env.listeners.get('subtitleAfterUpdate').size, 1)
|
|
env.emit('destroy')
|
|
assert.equal(env.listeners.get('subtitleAfterUpdate').size, 0)
|
|
})
|
|
|
|
test('Multiple subtitles legacy listener is removed on destroy and captured late callbacks are inert', async () => {
|
|
const env = host()
|
|
await env.factory({ subtitles: [] })(env.art)
|
|
const callback = [...env.listeners.get('subtitleUpdate')][0]
|
|
env.setActive([{ text: 'A' }, { text: 'B' }])
|
|
env.emit('destroy')
|
|
env.target.innerHTML = '<span>After destroy</span>'
|
|
callback('A')
|
|
assert.equal(env.target.textContent, 'After destroy')
|
|
assert.equal(env.listeners.get('subtitleUpdate').size, 0)
|
|
assert.equal(env.listeners.get('destroy').size, 0)
|
|
})
|
|
|
|
test('Multiple subtitles legacy listener registration failure unwinds both listeners', async () => {
|
|
const env = host()
|
|
const on = env.art.on
|
|
const error = new Error('Listener registration failed')
|
|
env.art.on = (name, callback) => {
|
|
on(name, callback)
|
|
if (name === 'subtitleUpdate')
|
|
throw error
|
|
return env.art
|
|
}
|
|
await assert.rejects(env.factory({ subtitles: [] })(env.art), value => value === error)
|
|
assert.equal(env.listeners.get('subtitleUpdate').size, 0)
|
|
assert.equal(env.listeners.get('destroy').size, 0)
|
|
assert.equal(env.liveBlobs.size, 0)
|
|
})
|