mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-21 12:41:20 -08:00
fix(docs): [SITE-LOAD-01] serialize example loading and preserve navigation
This commit is contained in:
1 parent
76499e7a3e
commit
647f3e7f12
29 files changed
+1977
-256
No files matched your search
@@ -1,5 +1,9 @@
|
||||
# Tests and fixture ownership
|
||||
|
||||
`site-loading.test.js` freezes the old mobile loader's failure/race reproduction
|
||||
and checks shared loader ownership, retries/cancellation, query encoding and
|
||||
language rules. Actual local pages and Monaco are covered by site-loading.spec.js.
|
||||
|
||||
Editor tooling lives in `scripts/editor-declarations/`; see its README.
|
||||
`test/editor-types.test.js` checks standalone globals and module consumers with
|
||||
current/old compilers, Chapter/VAST regressions, generated files and library-list
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Browser regression entry
|
||||
|
||||
`site-loading.spec.js` covers actual desktop/mobile loading, dependency order,
|
||||
failure/retry/AMD restoration, example priority, repeated Run and stale responses,
|
||||
plus Run Code and language routing. External analytics/ads are inert test routes;
|
||||
no advertising service or native device acceptance is implied. Keep this browser
|
||||
run serial with heavy compiler baselines after observing local setup contention.
|
||||
|
||||
`editor-declarations.spec.js` loads the repository Monaco with the 22 declarations
|
||||
actually listed in `common.js`, checks positive/negative consumers and runs emitted
|
||||
Chapter code through ready/destroy with controlled media. VAST coverage is types
|
||||
|
||||
@@ -4,7 +4,7 @@ import { expect, test } from './fixtures.js'
|
||||
|
||||
test('Monaco checks all editor declarations together and runs the Chapter consumer', async ({ page }, testInfo) => {
|
||||
const common = fs.readFileSync('docs/assets/js/common.js', 'utf8')
|
||||
const list = common.match(/let libUris = \[([\s\S]*?)\]/)?.[1] || ''
|
||||
const list = common.match(/(?:let|const) libUris = \[([\s\S]*?)\]/)?.[1] || ''
|
||||
const names = [...list.matchAll(/'\.\/assets\/ts\/([^']+\.d\.ts)'/g)].map(match => match[1])
|
||||
expect(names).toHaveLength(22)
|
||||
await testInfo.attach('editor-declaration-inputs', { contentType: 'application/json', body: JSON.stringify(Object.fromEntries(names.map(name => [name, hash(fs.readFileSync(`docs/assets/ts/${name}`))]))) })
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
import fs from 'node:fs'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
test('mobile page runs dependencies in order and plays the selected code with no AMD property leak', async ({
|
||||
page,
|
||||
}) => {
|
||||
let release
|
||||
let started
|
||||
const gate = new Promise((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
const firstRequested = new Promise((resolve) => {
|
||||
started = resolve
|
||||
})
|
||||
let secondRequested = false
|
||||
await page.route('https://**/*', route =>
|
||||
route.fulfill({ status: 200, body: '' }))
|
||||
await page.route('**/site-first.js', async (route) => {
|
||||
started()
|
||||
await gate
|
||||
await route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: 'window.order = ["first"]; window.firstDependency = true;',
|
||||
})
|
||||
})
|
||||
await page.route('**/site-second.js', (route) => {
|
||||
secondRequested = true
|
||||
return route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: 'if (!window.firstDependency) throw new Error("Dependency order"); window.order.push("second");',
|
||||
})
|
||||
})
|
||||
const code
|
||||
= 'window.order.push("code"); window.art = new Artplayer({ container: ".artplayer-app", url: "/test/pattern.mp4", muted: true });'
|
||||
await page.goto(
|
||||
`/mobile.html?libs=${encodeURIComponent('./site-first.js\n./site-second.js')}&code=${encodeURIComponent(code)}`,
|
||||
{ waitUntil: 'domcontentloaded' },
|
||||
)
|
||||
await firstRequested
|
||||
expect(secondRequested).toBe(false)
|
||||
release()
|
||||
await page.waitForFunction(() => window.art?.isReady)
|
||||
expect(await page.evaluate(() => window.order)).toEqual([
|
||||
'first',
|
||||
'second',
|
||||
'code',
|
||||
])
|
||||
expect(await page.evaluate(() => Object.hasOwn(window, 'define'))).toBe(
|
||||
false,
|
||||
)
|
||||
await page.evaluate(() => window.art.play())
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.art.currentTime))
|
||||
.toBeGreaterThan(0)
|
||||
await page.evaluate(() => window.art.pause())
|
||||
expect(await page.evaluate(() => window.art.playing)).toBe(false)
|
||||
await page.evaluate(() => window.art.destroy(true))
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
})
|
||||
|
||||
test('real script failure is retryable and cancellation restores the original AMD function', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/test/player.html')
|
||||
await page.addScriptTag({ url: '/assets/js/loader.js' })
|
||||
let attempts = 0
|
||||
await page.route('**/site-retry.js', route =>
|
||||
route.fulfill(
|
||||
++attempts === 1
|
||||
? { status: 404, body: 'missing' }
|
||||
: {
|
||||
contentType: 'text/javascript',
|
||||
body: 'window.retryLoaded = true;',
|
||||
},
|
||||
))
|
||||
const result = await page.evaluate(async () => {
|
||||
const original = Object.assign(() => {}, { amd: {} })
|
||||
Object.defineProperty(window, 'define', {
|
||||
value: original,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
})
|
||||
let error = ''
|
||||
try {
|
||||
await window.ArtplayerDocsLoader.loadLibraries('/site-retry.js')
|
||||
}
|
||||
catch (cause) {
|
||||
error = cause.message
|
||||
}
|
||||
const restored
|
||||
= window.define === original
|
||||
&& !Object.getOwnPropertyDescriptor(window, 'define').enumerable
|
||||
await window.ArtplayerDocsLoader.loadLibraries('/site-retry.js')
|
||||
await window.ArtplayerDocsLoader.loadLibraries('/site-retry.js')
|
||||
return {
|
||||
error,
|
||||
restored,
|
||||
retryLoaded: window.retryLoaded,
|
||||
nodes: document.querySelectorAll('script[src$="site-retry.js"]').length,
|
||||
after: window.define === original,
|
||||
}
|
||||
})
|
||||
expect(result.error).toContain('Loading script failed')
|
||||
expect(result).toMatchObject({
|
||||
restored: true,
|
||||
retryLoaded: true,
|
||||
nodes: 1,
|
||||
after: true,
|
||||
})
|
||||
expect(attempts).toBe(2)
|
||||
const cancellation = await page.evaluate(async () => {
|
||||
const original = window.define
|
||||
const append = document.head.appendChild
|
||||
document.head.appendChild = function (node) {
|
||||
if (node.src?.endsWith('/site-pending.js')) {
|
||||
queueMicrotask(() => window.ArtplayerDocsLoader.dispose())
|
||||
return node
|
||||
}
|
||||
return append.call(this, node)
|
||||
}
|
||||
try {
|
||||
let error = ''
|
||||
try {
|
||||
await window.ArtplayerDocsLoader.loadLibraries('/site-pending.js')
|
||||
}
|
||||
catch (cause) {
|
||||
error = cause.message
|
||||
}
|
||||
return { error, restored: window.define === original }
|
||||
}
|
||||
finally {
|
||||
document.head.appendChild = append
|
||||
}
|
||||
})
|
||||
expect(cancellation.error).toContain('Cancelled')
|
||||
expect(cancellation.restored).toBe(true)
|
||||
})
|
||||
|
||||
test('desktop editor preserves example priority and reuses loaded dependencies when Run is clicked', async ({
|
||||
page,
|
||||
}) => {
|
||||
let loads = 0
|
||||
await page.route('https://**/*', route =>
|
||||
route.fulfill({ status: 200, body: '' }))
|
||||
await page.route('**/site-lib.js', (route) => {
|
||||
loads++
|
||||
return route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: 'window.siteLibraryLoaded = true;',
|
||||
})
|
||||
})
|
||||
await page.route('**/assets/example/site.fixture.js', route =>
|
||||
route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: 'if (!window.siteLibraryLoaded) throw new Error("Missing dependency"); window.siteRuns = (window.siteRuns || 0) + 1; var art = new Artplayer({ container: ".artplayer-app", url: "/test/pattern.mp4", muted: true });',
|
||||
}))
|
||||
await page.goto(
|
||||
`/?libs=${encodeURIComponent('./site-lib.js')}&example=site.fixture&code=${encodeURIComponent('throw new Error("wrong priority")')}`,
|
||||
{ waitUntil: 'domcontentloaded' },
|
||||
)
|
||||
await page.waitForFunction(() => window.art?.isReady)
|
||||
const before = await page.evaluate(() => {
|
||||
window.previousArt = window.art
|
||||
return window.art.id
|
||||
})
|
||||
await page.locator('.run').click()
|
||||
await page.waitForFunction(
|
||||
id => window.art?.isReady && window.art.id !== id,
|
||||
before,
|
||||
)
|
||||
expect(
|
||||
await page.evaluate(() => ({
|
||||
runs: window.siteRuns,
|
||||
destroyed: window.previousArt.isDestroy,
|
||||
instances: window.Artplayer.instances.length,
|
||||
amd: typeof window.define,
|
||||
})),
|
||||
).toEqual({ runs: 2, destroyed: true, instances: 1, amd: 'function' })
|
||||
expect(loads).toBe(1)
|
||||
await page.evaluate(() => window.art.destroy(true))
|
||||
})
|
||||
|
||||
test('Run Code handles nested clicks, encodes dependencies and stays local on 127.0.0.1', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/test/player.html')
|
||||
await page.evaluate(() => {
|
||||
window.opened = []
|
||||
window.open = (url) => {
|
||||
window.opened.push(url)
|
||||
return null
|
||||
}
|
||||
document.body.innerHTML
|
||||
= '<div className="run-code" data-libs="https://cdn.example/lib.js?a=1&b=2"><span id="run-inner">Run</span></div><div><pre><code>var x = "你好+a&b";</code></pre></div><div class="run-code" id="missing">Missing code</div>'
|
||||
})
|
||||
const code = fs.readFileSync(
|
||||
'packages/artplayer-vitepress/docs/public/main.js',
|
||||
'utf8',
|
||||
)
|
||||
await page.addScriptTag({ content: code })
|
||||
await page.addScriptTag({ content: code })
|
||||
await page.locator('#run-inner').click()
|
||||
await page.locator('#missing').click()
|
||||
const opened = await page.evaluate(() => window.opened)
|
||||
expect(opened).toHaveLength(1)
|
||||
const url = new URL(opened[0])
|
||||
expect(url.origin).toBe('http://127.0.0.1:8082')
|
||||
expect(url.searchParams.get('libs')).toBe(
|
||||
'https://cdn.example/lib.js?a=1&b=2',
|
||||
)
|
||||
expect(url.searchParams.get('code')).toBe('var x = "你好+a&b";')
|
||||
expect(page.url()).toContain('/test/player.html')
|
||||
})
|
||||
|
||||
test('first-language redirect preserves the translated deep link and later explicit Chinese selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
const code = fs.readFileSync(
|
||||
'packages/artplayer-vitepress/docs/public/main.js',
|
||||
'utf8',
|
||||
)
|
||||
await page.addInitScript(() =>
|
||||
Object.defineProperty(navigator, 'language', { get: () => 'en-US' }),
|
||||
)
|
||||
await page.route('https://docs.example/**', route =>
|
||||
route.fulfill({
|
||||
contentType: 'text/html',
|
||||
body: `<html><body>Documentation<script>${code}</script></body></html>`,
|
||||
}))
|
||||
await page.goto('https://docs.example/document/start/option.html?x=1#url')
|
||||
await page.waitForURL(
|
||||
'https://docs.example/document/en/start/option.html?x=1#url',
|
||||
)
|
||||
expect(await page.evaluate(() => localStorage.getItem('lang-init'))).toBe(
|
||||
'true',
|
||||
)
|
||||
await page.goto('https://docs.example/document/start/option.html')
|
||||
expect(page.url()).toBe('https://docs.example/document/start/option.html')
|
||||
})
|
||||
|
||||
test('a late initial example response cannot overwrite a newer editor Run', async ({
|
||||
page,
|
||||
}) => {
|
||||
let release
|
||||
let started
|
||||
const gate = new Promise((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
const requested = new Promise((resolve) => {
|
||||
started = resolve
|
||||
})
|
||||
await page.route('https://**/*', route => route.fulfill({ body: '' }))
|
||||
await page.addInitScript(() => {
|
||||
const original = window.fetch
|
||||
window.fetch = async (...args) => {
|
||||
const response = await original(...args)
|
||||
if (String(args[0]).includes('site.slow.js')) {
|
||||
const read = response.text.bind(response)
|
||||
response.text = async () => {
|
||||
const text = await read()
|
||||
window.slowReceived = true
|
||||
return text
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
})
|
||||
await page.route('**/assets/example/site.slow.js', async (route) => {
|
||||
started()
|
||||
await gate
|
||||
await route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: 'window.staleRan = true;',
|
||||
})
|
||||
})
|
||||
await page.goto('/?example=site.slow', { waitUntil: 'domcontentloaded' })
|
||||
await requested
|
||||
await page.evaluate(() => {
|
||||
const model = window.monaco.editor
|
||||
.getModels()
|
||||
.find(model => model.getLanguageId() === 'javascript')
|
||||
model.setValue(
|
||||
'window.newerRan = true; var art = new Artplayer({ container: ".artplayer-app", url: "/test/pattern.mp4", muted: true });',
|
||||
)
|
||||
})
|
||||
await page.locator('.run').click()
|
||||
await page.waitForFunction(() => window.art?.isReady)
|
||||
release()
|
||||
await page.waitForFunction(() => window.slowReceived)
|
||||
expect(
|
||||
await page.evaluate(() => ({
|
||||
newer: window.newerRan,
|
||||
stale: !!window.staleRan,
|
||||
ready: window.art.isReady,
|
||||
instances: window.Artplayer.instances.length,
|
||||
})),
|
||||
).toEqual({ newer: true, stale: false, ready: true, instances: 1 })
|
||||
await page.evaluate(() => window.art.destroy(true))
|
||||
})
|
||||
|
||||
test('denied storage keeps the requested documentation page without a redirect loop', async ({
|
||||
page,
|
||||
}) => {
|
||||
const code = fs.readFileSync(
|
||||
'packages/artplayer-vitepress/docs/public/main.js',
|
||||
'utf8',
|
||||
)
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'language', { get: () => 'en-US' })
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
get: () => {
|
||||
throw new DOMException('Blocked', 'SecurityError')
|
||||
},
|
||||
})
|
||||
})
|
||||
let requests = 0
|
||||
await page.route('https://blocked.example/**', (route) => {
|
||||
requests++
|
||||
return route.fulfill({
|
||||
contentType: 'text/html',
|
||||
body: `<html><body>Documentation<script>${code}</script></body></html>`,
|
||||
})
|
||||
})
|
||||
await page.goto('https://blocked.example/document/start/option.html')
|
||||
expect(page.url()).toBe('https://blocked.example/document/start/option.html')
|
||||
expect(requests).toBe(1)
|
||||
expect(await page.evaluate(() => window['run-code-init'])).toBe(true)
|
||||
})
|
||||
@@ -0,0 +1,256 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Use the repository Node regression runner.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
import {
|
||||
createLibraryLoader,
|
||||
exampleSource,
|
||||
parameters,
|
||||
} from '../packages/artplayer-vitepress/browser/loader.ts'
|
||||
import {
|
||||
languageDestination,
|
||||
runCodeUrl,
|
||||
} from '../packages/artplayer-vitepress/browser/navigation.ts'
|
||||
|
||||
const turn = () => new Promise(resolve => setImmediate(resolve))
|
||||
function fixture() {
|
||||
class Element extends EventTarget {
|
||||
removed = false
|
||||
remove() {
|
||||
this.removed = true
|
||||
}
|
||||
|
||||
succeed() {
|
||||
this.onload?.()
|
||||
this.dispatchEvent(new Event('load'))
|
||||
}
|
||||
|
||||
fail() {
|
||||
this.onerror?.()
|
||||
this.dispatchEvent(new Event('error'))
|
||||
}
|
||||
}
|
||||
const nodes = []
|
||||
const head = {
|
||||
appendChild(element) {
|
||||
nodes.push(element)
|
||||
},
|
||||
}
|
||||
const host = {
|
||||
location: { href: 'http://localhost:8082/mobile.html' },
|
||||
define: Object.assign(() => {}, { amd: {} }),
|
||||
document: {
|
||||
head,
|
||||
createElement: () => new Element(),
|
||||
querySelector: () => head,
|
||||
},
|
||||
}
|
||||
return { host, nodes, head }
|
||||
}
|
||||
|
||||
test('fixed old mobile loader reproduces failure and overlapping AMD restoration defects', async () => {
|
||||
const source = execFileSync(
|
||||
'git',
|
||||
[
|
||||
'show',
|
||||
'76499e7a3e033c8350ff495ba8bfff4ca5c08d25:docs/assets/js/mobile.js',
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
)
|
||||
const end = source.indexOf(' var _getURLParameters')
|
||||
assert(end > 0)
|
||||
const old = `${source.slice(0, end)} window.legacy = {loadScript, loadLib}; })();`
|
||||
const failed = fixture()
|
||||
vm.runInNewContext(old, {
|
||||
window: failed.host,
|
||||
document: failed.host.document,
|
||||
Artplayer: {},
|
||||
})
|
||||
const failure = failed.host.legacy.loadScript('missing.js')
|
||||
failed.nodes[0].fail()
|
||||
await assert.rejects(failure, /Loading script failed/)
|
||||
assert.equal(failed.host.define, undefined)
|
||||
const parallel = fixture()
|
||||
vm.runInNewContext(old, {
|
||||
window: parallel.host,
|
||||
document: parallel.host.document,
|
||||
Artplayer: {},
|
||||
})
|
||||
const loaded = parallel.host.legacy.loadLib('first.js%0Asecond.js')
|
||||
assert.equal(
|
||||
parallel.nodes.length,
|
||||
2,
|
||||
'Old code starts both scripts before dependencies resolve',
|
||||
)
|
||||
parallel.nodes[0].succeed()
|
||||
parallel.nodes[1].succeed()
|
||||
await loaded
|
||||
assert.equal(
|
||||
parallel.host.define,
|
||||
undefined,
|
||||
'Second request saved the temporarily cleared define',
|
||||
)
|
||||
})
|
||||
|
||||
test('shared loader serializes batches, caches successes, retries failures and restores exact AMD descriptor', async () => {
|
||||
const { host, nodes } = fixture()
|
||||
const original = Object.getOwnPropertyDescriptor(host, 'define')
|
||||
const loader = createLibraryLoader(host)
|
||||
const first = loader.loadLibraries(
|
||||
encodeURIComponent(' ./first.js \n./second.js'),
|
||||
)
|
||||
const second = loader.loadLibraries(
|
||||
encodeURIComponent('./first.js\n./third.css'),
|
||||
)
|
||||
await turn()
|
||||
assert.equal(nodes.length, 1)
|
||||
assert.equal(host.define, undefined)
|
||||
nodes[0].succeed()
|
||||
await turn()
|
||||
assert.equal(nodes.length, 2)
|
||||
const rejected = assert.rejects(first, /Loading script failed/)
|
||||
nodes[1].fail()
|
||||
await rejected
|
||||
await turn()
|
||||
assert.equal(nodes.length, 3)
|
||||
assert(nodes[1].removed)
|
||||
assert.deepEqual(Object.getOwnPropertyDescriptor(host, 'define'), original)
|
||||
nodes[2].succeed()
|
||||
assert.deepEqual(await second, ['./first.js', './third.css'])
|
||||
const retry = loader.loadLibraries('./second.js')
|
||||
await turn()
|
||||
assert.equal(nodes.length, 4)
|
||||
nodes[3].succeed()
|
||||
await retry
|
||||
assert.deepEqual(Object.getOwnPropertyDescriptor(host, 'define'), original)
|
||||
await assert.rejects(loader.loadLibraries('%bad-encoding'), URIError)
|
||||
assert.equal(nodes.length, 4)
|
||||
})
|
||||
|
||||
test('loader restores absence/accessors and synchronous failure, cancels pending work and rejects immutable AMD state', async () => {
|
||||
for (const mode of [
|
||||
'absent',
|
||||
'accessor',
|
||||
'append-failure',
|
||||
'dispose',
|
||||
'immutable',
|
||||
]) {
|
||||
const { host, nodes, head } = fixture()
|
||||
if (mode === 'absent')
|
||||
Reflect.deleteProperty(host, 'define')
|
||||
if (mode === 'accessor') {
|
||||
Object.defineProperty(host, 'define', {
|
||||
get: () => 42,
|
||||
configurable: true,
|
||||
})
|
||||
}
|
||||
if (mode === 'immutable') {
|
||||
Object.defineProperty(host, 'define', {
|
||||
value: 42,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
})
|
||||
}
|
||||
if (mode === 'append-failure') {
|
||||
head.appendChild = () => {
|
||||
throw new Error('append failed')
|
||||
}
|
||||
}
|
||||
const before = Object.getOwnPropertyDescriptor(host, 'define')
|
||||
const loader = createLibraryLoader(host)
|
||||
const pending = loader.loadLibraries('test.js')
|
||||
const fails = ['append-failure', 'dispose', 'immutable'].includes(mode)
|
||||
const rejection = fails ? assert.rejects(pending) : null
|
||||
await turn()
|
||||
if (mode === 'dispose')
|
||||
loader.dispose()
|
||||
else if (!fails)
|
||||
nodes[0].succeed()
|
||||
if (rejection)
|
||||
await rejection
|
||||
else await pending
|
||||
assert.deepEqual(
|
||||
Object.getOwnPropertyDescriptor(host, 'define'),
|
||||
before,
|
||||
mode,
|
||||
)
|
||||
if (mode === 'dispose') {
|
||||
assert(nodes[0].removed)
|
||||
await assert.rejects(loader.loadLibraries('later.js'), /disposed/)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('URL and example contracts preserve literal plus, last duplicate and explicit example priority inputs', async () => {
|
||||
assert.deepEqual(
|
||||
parameters(
|
||||
'http://localhost/?code=a+b%2Bc&libs=first&libs=second&example=mobile#ignored',
|
||||
),
|
||||
{ code: 'a+b%2Bc', libs: 'second', example: 'mobile' },
|
||||
)
|
||||
assert.equal(
|
||||
await exampleSource('mobile', async (url) => {
|
||||
assert.equal(url, './assets/example/mobile.js')
|
||||
return new Response('var art = 1')
|
||||
}),
|
||||
'var art = 1',
|
||||
)
|
||||
await assert.rejects(
|
||||
exampleSource(
|
||||
'missing',
|
||||
async () => new Response('404 HTML', { status: 404 }),
|
||||
),
|
||||
/404/,
|
||||
)
|
||||
const libs = 'https://cdn.example/lib.js?a=1&b=two\n./theme.css'
|
||||
const code = 'var x = "a+b&你好";'
|
||||
for (const host of ['localhost', '127.0.0.1', '[::1]']) {
|
||||
const url = new URL(runCodeUrl({ hostname: host }, libs, code))
|
||||
assert.equal(url.host, `${host}:8082`)
|
||||
assert.equal(url.searchParams.get('libs'), libs)
|
||||
assert.equal(url.searchParams.get('code'), code)
|
||||
}
|
||||
assert.equal(
|
||||
new URL(runCodeUrl({ hostname: 'artplayer.org' }, '', code)).origin,
|
||||
'https://artplayer.org',
|
||||
)
|
||||
})
|
||||
|
||||
test('language routing preserves translated deep links and explicit choices without redirecting local previews', () => {
|
||||
const paths = ['', 'index.html', 'start/option.html']
|
||||
const choose = (url, lang = 'en-US', initialized = false) =>
|
||||
languageDestination(url, lang, initialized, paths)
|
||||
assert.deepEqual(
|
||||
choose('https://artplayer.org/document/start/option.html?x=1#url'),
|
||||
{
|
||||
remember: true,
|
||||
target: 'https://artplayer.org/document/en/start/option.html?x=1#url',
|
||||
},
|
||||
)
|
||||
assert.deepEqual(
|
||||
choose('https://artplayer.org/document/plugin/danmuku.html#x'),
|
||||
{ remember: true, target: 'https://artplayer.org/document/en/' },
|
||||
)
|
||||
assert.deepEqual(
|
||||
choose('https://artplayer.org/document/en/start/option.html'),
|
||||
{ remember: true, target: null },
|
||||
)
|
||||
for (const host of ['localhost', '127.0.0.1', '[::1]']) {
|
||||
assert.deepEqual(choose(`http://${host}:5173/document/`), {
|
||||
remember: false,
|
||||
target: null,
|
||||
})
|
||||
}
|
||||
for (const [language, initialized] of [
|
||||
['zh-CN', false],
|
||||
['ZH-tw', false],
|
||||
['en', true],
|
||||
['', false],
|
||||
]) {
|
||||
assert.deepEqual(
|
||||
choose('https://artplayer.org/document/', language, initialized),
|
||||
{ remember: false, target: null },
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user