/** * `src/web/locale-pref.ts` —— 语言偏好写进 `settings.yaml` 的**按行对账**逻辑。 * * 为什么专门测它:这个函数**直接改用户的 `settings.yaml`** —— 写坏了 dsh 会拒绝加载整个文档 * (用户实例直接起不来)。所以这里钉的是三件事: * · 只碰 `locale.preference` 那两行,**其它块 / 注释 / 顺序一律原样保留**; * · 幂等(值相同 ⇒ `changed: false`,调用方据此跳过写盘); * · 行尾风格跟着原文件走(CRLF 文件不会被写成混合行尾)。 */ import { test } from 'node:test' import assert from 'node:assert/strict' import { LOCALE_IDS, isLocaleId, reconcileLocalePreference } from '../lib/web/locale-pref.js' test('LOCALE_IDS 与官方 dsh-client-locale 一致(en 是官方 FALLBACK)', () => { assert.deepEqual([...LOCALE_IDS], ['en', 'zh']) assert.equal(isLocaleId('en'), true) assert.equal(isLocaleId('zh'), true) assert.equal(isLocaleId('ja'), false) assert.equal(isLocaleId(''), false) assert.equal(isLocaleId(undefined), false) }) test('空文档 ⇒ 建出 locale 块', () => { const r = reconcileLocalePreference('', 'zh') assert.equal(r.changed, true) assert.equal(r.text, 'locale:\n preference: zh\n') }) test('没有 locale 块 ⇒ 追加,且不动已有内容', () => { const src = 'llm-pi-ai:\n providers:\n my-gw:\n baseURL: https://x\n' const r = reconcileLocalePreference(src, 'zh') assert.equal(r.changed, true) assert.ok(r.text.startsWith(src), '原有内容必须原样在前') assert.ok(r.text.includes('locale:\n preference: zh')) }) test('已有 locale 块但没有 preference ⇒ 紧跟块头插入一行', () => { const src = 'locale:\n other: keep\nfoo: bar\n' const r = reconcileLocalePreference(src, 'zh') assert.equal(r.text, 'locale:\n preference: zh\n other: keep\nfoo: bar\n') }) test('值不同 ⇒ 原地替换(只改那一行)', () => { const src = 'a: 1\nlocale:\n preference: en\nb: 2\n' const r = reconcileLocalePreference(src, 'zh') assert.equal(r.changed, true) assert.equal(r.text, 'a: 1\nlocale:\n preference: zh\nb: 2\n') }) test('值相同 ⇒ 幂等(changed: false,文本一字不动)', () => { const src = 'locale:\n preference: zh\n' const r = reconcileLocalePreference(src, 'zh') assert.equal(r.changed, false) assert.equal(r.text, src) }) test('注释与其它块一律保留', () => { const src = '# 我自己的注释\nllm-pi-ai:\n # 内层注释\n providers: {}\nlocale:\n preference: en\n' const r = reconcileLocalePreference(src, 'zh') assert.ok(r.text.includes('# 我自己的注释')) assert.ok(r.text.includes(' # 内层注释')) assert.ok(r.text.includes(' preference: zh')) assert.equal(r.text.split('\n').length, src.split('\n').length, '行数不变(没多写也没少写)') }) test('CRLF 文档 ⇒ 保持 CRLF(不产生混合行尾)', () => { const src = 'a: 1\r\nlocale:\r\n preference: en\r\n' const r = reconcileLocalePreference(src, 'zh') assert.ok(r.text.includes(' preference: zh\r\n')) assert.equal(r.text.includes('\n\n'), false) assert.equal(r.text.replace(/\r\n/g, '').includes('\n'), false, '不许出现裸 LF') }) test('两次调用等价(第二次不再 changed)', () => { const first = reconcileLocalePreference('x: 1\n', 'zh') const second = reconcileLocalePreference(first.text, 'zh') assert.equal(second.changed, false) assert.equal(second.text, first.text) })