Files
dsh_shenxian/test/i18n-brand.test.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

94 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* `web/i18n.js` 的品牌文案渲染回归(2026-09-19,档案 134 配套)。
*
* 为什么单独钉这一条:品牌标识从 DeepSeek 图形换成了**文字**(中文「能力网络」(2026-09-19
* 定名)/英文及其他语言 `CapabilityNet`)。词条漏配或 key 写错时,i18n 会**静默回退英文**
* —— 页面上看不出报错,只有中文用户看到英文名。这类"静默失败"必须靠测试钉住。
*
* 做法:用 `node:vm` 跑**仓库里那份真实的 `web/i18n.js`**(不是复制一遍逻辑),
* 配一个够用的最小 DOM 桩,断言六种语言解析路径下的**实际渲染结果**。
*
* ⚠️ 桩里必须注入 `URLSearchParams` / `URL`:`vm` 上下文只含 ECMAScript 内置、
* 不含 Web API;少了它们 `new URLSearchParams(location.search)` 会抛 ReferenceError,
* 被 i18n.js 的 try/catch 吞掉 ⇒ 表现为"`?lang=` 不生效"的**假失败**(第一版踩过)。
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import vm from 'node:vm'
const SRC = readFileSync(new URL('../web/i18n.js', import.meta.url), 'utf8')
/** 造一个够 i18n.js 用的最小元素桩(只需要 getAttribute / textContent)。 */
function makeEl(attrs = {}) {
return {
_attrs: { ...attrs },
textContent: attrs.__text ?? '',
className: '',
innerHTML: '',
style: {},
getAttribute(name) {
return Object.prototype.hasOwnProperty.call(this._attrs, name) ? this._attrs[name] : null
},
setAttribute(name, value) {
this._attrs[name] = value
},
appendChild() {},
addEventListener() {},
querySelectorAll() {
return []
},
}
}
/** 在最小 DOM 桩里跑一次 i18n.js,返回品牌节点的实际渲染结果与解析出的语言。 */
function renderBrand({ search = '', cookie = '', language = 'en-US' } = {}) {
const brandNode = makeEl({ 'data-i18n': 'brand.name', __text: 'CapabilityNet' })
const sandbox = {
console,
setTimeout: () => {},
URLSearchParams,
URL,
document: {
readyState: 'complete',
cookie,
documentElement: makeEl(),
querySelectorAll(selector) {
return selector === '[data-i18n]' ? [brandNode] : []
},
createElement: () => makeEl(),
addEventListener: () => {},
},
navigator: { language, languages: [language] },
location: { search, href: `https://example.net/login.html${search}`, reload: () => {} },
window: {},
}
sandbox.window = sandbox
vm.createContext(sandbox)
vm.runInContext(SRC, sandbox, { filename: 'web/i18n.js' })
return { brand: brandNode.textContent, lang: sandbox.window.I18N.lang }
}
test('品牌文案:中文显示「能力网络」,英文及其他语言显示 CapabilityNet', () => {
const cases = [
['默认(浏览器 en-US)', { language: 'en-US' }, 'CapabilityNet'],
['中文(浏览器 zh-CN)', { language: 'zh-CN' }, '能力网络'],
['中文(?lang=zh)', { search: '?lang=zh', language: 'en-US' }, '能力网络'],
['英文(?lang=en 覆盖中文浏览器)', { search: '?lang=en', language: 'zh-CN' }, 'CapabilityNet'],
['cookie dsh_lang=zh', { cookie: 'dsh_lang=zh', language: 'en-US' }, '能力网络'],
['未支持语言(ja)⇒ 回退英文', { language: 'ja-JP' }, 'CapabilityNet'],
]
for (const [label, opts, expected] of cases) {
const got = renderBrand(opts)
assert.equal(got.brand, expected, label)
}
})
test('品牌文案:不会退化成裸 key、不留空', () => {
for (const language of ['en-US', 'zh-CN', 'ja-JP', 'de-DE']) {
const { brand } = renderBrand({ language })
assert.notEqual(brand, 'brand.name', `裸 key 泄露(${language})`)
assert.ok(brand.trim() !== '', `空文案(${language})`)
}
})