mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-22 05:11:20 -08:00
fix(core): [CORE-25] read defaults without navigator
This commit is contained in:
1 parent
4a299eb2d4
commit
3b016970f2
26 files changed
+916
-35
No files matched your search
@@ -1,5 +1,31 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (const locale of ['en-US', 'zh-CN']) {
|
||||
test.describe(`default language ${locale}`, () => {
|
||||
test.use({ locale })
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: language defaults follow the browser and explicit language still overrides`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
const result = await page.evaluate(() => {
|
||||
const container = document.createElement('div')
|
||||
document.body.append(container)
|
||||
const { Artplayer } = window
|
||||
const initial = new Artplayer({ container })
|
||||
const observed = { browser: navigator.language, defaults: Artplayer.option.lang, instance: initial.option.lang }
|
||||
initial.destroy()
|
||||
document.body.append(container)
|
||||
const explicit = new Artplayer({ container, lang: 'fr' })
|
||||
observed.explicit = explicit.option.lang
|
||||
observed.defaultAfterOverride = Artplayer.option.lang
|
||||
explicit.destroy()
|
||||
return observed
|
||||
})
|
||||
expect(result).toEqual({ browser: locale, defaults: locale.toLowerCase(), instance: locale.toLowerCase(), explicit: 'fr', defaultAfterOverride: locale.toLowerCase() })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: expanded option input preserves historical JS forms`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
|
||||
@@ -40,6 +40,55 @@ test('default values, keys and nested order match the published getter', () => {
|
||||
assert.deepEqual(Object.keys(current.Artplayer.option), Object.keys(published.Artplayer.option))
|
||||
})
|
||||
|
||||
test('candidate defaults are readable without navigator and keep independent option containers', () => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
|
||||
try {
|
||||
for (const mode of ['absent', 'undefined', 'null']) {
|
||||
assert(delete globalThis.navigator)
|
||||
if (mode !== 'absent')
|
||||
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: mode === 'null' ? null : undefined })
|
||||
const first = current.Artplayer.option
|
||||
const second = current.Artplayer.option
|
||||
assert.equal(first.lang, undefined, mode)
|
||||
assert(Object.hasOwn(first, 'lang'))
|
||||
assert.notEqual(first, second)
|
||||
assert.notEqual(first.subtitle, second.subtitle)
|
||||
assert.deepEqual(first.plugins, [])
|
||||
assert.equal(first.subtitle.onVttLoad('text'), 'text')
|
||||
assert.throws(() => resolveOption({ container: '#player' }, first), /option.lang/)
|
||||
assert.equal(resolveOption({ container: '#player', lang: 'en' }, first).lang, 'en')
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (descriptor)
|
||||
Object.defineProperty(globalThis, 'navigator', descriptor)
|
||||
else
|
||||
delete globalThis.navigator
|
||||
}
|
||||
})
|
||||
|
||||
test('candidate defaults read the current browser language on each access without caching it', () => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
|
||||
try {
|
||||
let language = 'EN-us'
|
||||
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: {
|
||||
get language() {
|
||||
return language
|
||||
},
|
||||
} })
|
||||
for (const value of ['EN-us', 'zh-CN', '']) {
|
||||
language = value
|
||||
assert.equal(current.Artplayer.option.lang, value.toLowerCase())
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (descriptor)
|
||||
Object.defineProperty(globalThis, 'navigator', descriptor)
|
||||
else
|
||||
delete globalThis.navigator
|
||||
}
|
||||
})
|
||||
|
||||
for (const [name, resolve, Artplayer] of [['published', oldResolve, published.Artplayer], ['workspace', resolveOption, current.Artplayer]]) {
|
||||
test(`resolved options preserve unknown fields, merge ownership and collection item identity: ${name}`, () => {
|
||||
const callback = () => {}
|
||||
|
||||
@@ -47,11 +47,12 @@ the actual child version. Reports and failure logs are `runtime-node-<version>.*
|
||||
the same package output directory. Old reports without source/toolchain fields must
|
||||
be rebuilt. CI restores canonical Node before running browser tools.
|
||||
|
||||
CORE-25 remains an explicit defect: both published and candidate static defaults throw
|
||||
without navigator. The fixture tests that exact failure on every runtime, then uses an
|
||||
identical controlled language for default-value comparison and restores the global.
|
||||
`knownRuntimeBlockers=1` prevents strict release success; ordinary observation tests
|
||||
passing do not mean the defect is fixed. See [Node evidence](../../refactor/changes/2026-09-13-CI-01-node-consumers.md).
|
||||
CORE-25 fixed candidate static defaults without navigator. The fixture preserves the
|
||||
published ReferenceError while requiring candidate main/legacy/ESM defaults to succeed,
|
||||
then uses an identical controlled language for comparison and restores the global.
|
||||
The fixture now reports zero known runtime blockers; it is still only core/chapter
|
||||
acceptance, not full release readiness. See [the fix](../../refactor/changes/2026-09-13-CORE-25-defaults-ssr.md)
|
||||
and the earlier [Node evidence](../../refactor/changes/2026-09-13-CI-01-node-consumers.md).
|
||||
|
||||
The optional core `artplayer/runtime` entry has eight additional strict consumer
|
||||
groups: TS 5.1.6 and 5.9.3 each check Node10 CommonJS, NodeNext CJS/ESM and Bundler.
|
||||
|
||||
@@ -189,10 +189,21 @@ const vm = require('node:vm');
|
||||
const defaultNavigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
|
||||
try {
|
||||
assert(delete globalThis.navigator)
|
||||
// CORE-25 tracks this reproduced published and candidate failure separately.
|
||||
assert.throws(() => core.option, { name: 'ReferenceError', message: 'navigator is not defined' })
|
||||
observations.defaultsWithoutNavigator = { name: 'ReferenceError', message: 'navigator is not defined', historical: !!expected.baseline, resolved: false }
|
||||
check('SSR.defaults-missing-navigator-known-failure', true)
|
||||
if (expected.baseline) {
|
||||
assert.throws(() => core.option, { name: 'ReferenceError', message: 'navigator is not defined' })
|
||||
observations.defaultsWithoutNavigator = { name: 'ReferenceError', message: 'navigator is not defined', historical: true, resolved: false }
|
||||
check('SSR.defaults-missing-navigator-known-failure', true)
|
||||
}
|
||||
else {
|
||||
const defaults = core.option
|
||||
assert.equal(defaults.lang, undefined)
|
||||
assert(Object.hasOwn(defaults, 'lang'))
|
||||
assert.notEqual(core.option, defaults)
|
||||
for (const Class of [esm.default, legacy.default, require('artplayer/legacy')])
|
||||
assert.equal(Class.option.lang, undefined)
|
||||
observations.defaultsWithoutNavigator = { historical: false, resolved: true, ownLang: true, langType: typeof defaults.lang }
|
||||
check('SSR.defaults-without-navigator', true)
|
||||
}
|
||||
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { language: 'en-US' } })
|
||||
observations.api = { static: shape(core), prototype: shape(core.prototype), emitter: shape(core.Emitter.prototype), factory: shape(chapter), defaults: JSON.parse(JSON.stringify(core.option, (_, value) => typeof value === 'function' ? '$function' : value)) }
|
||||
check('API.defaults-controlled-language', observations.api.defaults.lang === 'en-us')
|
||||
|
||||
@@ -4,6 +4,11 @@ import resolveOption from '../../packages/artplayer/src/option/resolve'
|
||||
|
||||
const input: Option = { container: '#player', url: 'video.mp4', subtitle: { encoding: 'utf-8' } }
|
||||
const resolved: ResolvedOption = resolveOption(input, createDefaults())
|
||||
const defaultLanguage: string | undefined = createDefaults().lang
|
||||
const validatedLanguage: string = resolved.lang.toLowerCase()
|
||||
void [defaultLanguage, validatedLanguage]
|
||||
// @ts-expect-error Unvalidated defaults may have no browser language.
|
||||
createDefaults().lang.toLowerCase()
|
||||
resolved.subtitle.onVttLoad(resolved.subtitle.url)
|
||||
resolved.thumbnails.column.toFixed()
|
||||
resolved.volume.toFixed()
|
||||
|
||||
Reference in new issue
Block a user