230 lines
7.8 KiB
JavaScript
230 lines
7.8 KiB
JavaScript
/**
|
||
* i18n 统一维护纯函数测试(V2:(b_key, b_locale) 复合键):
|
||
* 覆盖 index.vue 上移后的受管键收集、扁平行→透视、reconcile(补齐/跟名/失效清理)与保存 diff,
|
||
* 守护「ModuleI18nPanel 受控化」后数据编排逻辑不回退。
|
||
*/
|
||
import { describe, expect, it } from 'vitest'
|
||
import {
|
||
buildI18nChangeRequest,
|
||
buildI18nPivot,
|
||
collectManagedI18nItems,
|
||
reconcileI18nRows,
|
||
} from '@/views/module/module-management/utils'
|
||
|
||
const languages = [
|
||
{ code: 'zh-CN', name: '简体中文', default: true },
|
||
{ code: 'en-US', name: 'English', default: false },
|
||
]
|
||
const defaultLocale = 'zh-CN'
|
||
|
||
// V2:b_id 即模块编码
|
||
function demoModule(over = {}) {
|
||
return {
|
||
b_id: 'demo',
|
||
b_module_type: 'data',
|
||
b_name: '演示模块',
|
||
b_i18n: null,
|
||
...over,
|
||
}
|
||
}
|
||
|
||
function demoField(over = {}) {
|
||
return { b_field: 'name', b_name: '姓名', b_i18n: null, ...over }
|
||
}
|
||
|
||
describe('collectManagedI18nItems 受管键收集', () => {
|
||
it('收集模块名与字段名的受管键', () => {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
expect(items.map((i) => i.key)).toEqual(['module.demo', 'field.demo.name'])
|
||
expect(items[0].id).toBe('module:module.demo')
|
||
expect(items[1].id).toBe('field:field.demo.name')
|
||
expect(items[0].defaultValue).toBe('演示模块')
|
||
expect(items[1].defaultValue).toBe('姓名')
|
||
})
|
||
|
||
it('收集界面配置分组标题的受管键:手填键优先,未填按 group.{模块}.{schema}.{编号} 派生', () => {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [],
|
||
schemas: [
|
||
{
|
||
schemaType: 'view',
|
||
schema: {
|
||
schemaVersion: 1,
|
||
children: [
|
||
{
|
||
type: 'group',
|
||
id: 'group1',
|
||
title: '基础信息',
|
||
i18n: 'group.demo.basic',
|
||
children: [],
|
||
},
|
||
{ type: 'group', id: 'group2', title: '费用信息', children: [] },
|
||
],
|
||
},
|
||
},
|
||
],
|
||
})
|
||
expect(items.map((i) => i.key)).toContain('group.demo.basic')
|
||
expect(items.find((i) => i.key === 'group.demo.basic').defaultValue).toBe('基础信息')
|
||
// 未手填键的分组:按 schema 类型 + 分组编号派生,标题原文作为默认语言值
|
||
const derived = items.find((i) => i.key === 'group.demo.view.group2')
|
||
expect(derived.defaultValue).toBe('费用信息')
|
||
expect(derived.id).toBe('group:group.demo.view.group2')
|
||
})
|
||
|
||
it('分组缺失编号(老数据未归一化)时不派生病造键', () => {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [],
|
||
schemas: [
|
||
{
|
||
schemaType: 'edit',
|
||
schema: { schemaVersion: 1, children: [{ type: 'group', title: '基础信息' }] },
|
||
},
|
||
],
|
||
})
|
||
expect(items.filter((i) => i.key.startsWith('group.'))).toEqual([])
|
||
})
|
||
|
||
it('模块编码为空时不产出受管键(新建未填编码)', () => {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule({ b_id: '' }),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
expect(items).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('buildI18nPivot / reconcileI18nRows', () => {
|
||
it('扁平行转透视后 reconcile 补齐受管行并跟随默认语言', () => {
|
||
const flat = [{ b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' }]
|
||
const pivot = buildI18nPivot(flat)
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
const rows = reconcileI18nRows(pivot, items, { defaultLocale, languages })
|
||
|
||
const byKey = new Map(rows.map((r) => [r.b_key, r]))
|
||
expect(byKey.size).toBe(2)
|
||
// 手工翻译保留
|
||
expect(byKey.get('field.demo.name')['en-US']).toBe('Name')
|
||
// 受管默认语言跟随当前名称
|
||
expect(byKey.get('field.demo.name')['zh-CN']).toBe('姓名')
|
||
expect(byKey.get('module.demo')['zh-CN']).toBe('演示模块')
|
||
expect(byKey.get('field.demo.name')._managedId).toBe('field:field.demo.name')
|
||
})
|
||
|
||
it('名称变化后 reconcile 自动跟名,人工翻译不被覆盖', () => {
|
||
const flat = [{ b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' }]
|
||
let rows = reconcileI18nRows(
|
||
buildI18nPivot(flat),
|
||
collectManagedI18nItems({ module: demoModule(), fields: [demoField()], schemas: [] }),
|
||
{ defaultLocale, languages },
|
||
)
|
||
rows = reconcileI18nRows(
|
||
rows,
|
||
collectManagedI18nItems({
|
||
module: demoModule({ b_name: '演示模块V2' }),
|
||
fields: [demoField({ b_name: '姓名V2' })],
|
||
schemas: [],
|
||
}),
|
||
{ defaultLocale, languages },
|
||
)
|
||
const byKey = new Map(rows.map((r) => [r.b_key, r]))
|
||
expect(byKey.get('module.demo')['zh-CN']).toBe('演示模块V2')
|
||
expect(byKey.get('field.demo.name')['zh-CN']).toBe('姓名V2')
|
||
expect(byKey.get('field.demo.name')['en-US']).toBe('Name')
|
||
})
|
||
|
||
it('字段删除后:无翻译的自动补齐行被清理,带翻译的保留为普通行', () => {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
const rows = reconcileI18nRows(buildI18nPivot([]), items, { defaultLocale, languages })
|
||
expect(rows.some((r) => r._managedId === 'field:field.demo.name')).toBe(true)
|
||
|
||
// 字段消失:受管行无持久翻译,应被清理
|
||
const next = reconcileI18nRows(rows, items.slice(0, 1), { defaultLocale, languages })
|
||
expect(next.some((r) => r.b_key === 'field.demo.name')).toBe(false)
|
||
})
|
||
})
|
||
|
||
describe('buildI18nChangeRequest 保存 diff', () => {
|
||
const originalFlat = [
|
||
{ b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' },
|
||
]
|
||
|
||
function draftRows() {
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
return reconcileI18nRows(buildI18nPivot(originalFlat), items, { defaultLocale, languages })
|
||
}
|
||
|
||
it('首存新增受管默认语言 cell,已存在值不重复插入', () => {
|
||
const req = buildI18nChangeRequest({
|
||
draftRows: draftRows(),
|
||
originalFlat,
|
||
languages,
|
||
})
|
||
expect(req).not.toBeNull()
|
||
expect(req.key_field).toBe('b_key,b_locale')
|
||
const keys = (req.inserts || []).map((r) => `${r.b_key}|${r.b_locale}`).sort()
|
||
expect(keys).toEqual(['field.demo.name|zh-CN', 'module.demo|zh-CN'])
|
||
// en-US 未变化 → 不产生 update
|
||
expect(req.updates || []).toHaveLength(0)
|
||
})
|
||
|
||
it('修改语言值只产生对应 cell 的 update(按 b_key + b_locale 定位)', () => {
|
||
const rows = draftRows()
|
||
rows.find((r) => r.b_key === 'field.demo.name')['en-US'] = 'NewName'
|
||
const req = buildI18nChangeRequest({
|
||
draftRows: rows,
|
||
originalFlat,
|
||
languages,
|
||
})
|
||
expect(
|
||
req.updates.some(
|
||
(r) => r.b_key === 'field.demo.name' && r.b_locale === 'en-US' && r.b_value === 'NewName',
|
||
),
|
||
).toBe(true)
|
||
// 复合主键定位,不再依赖 b_id
|
||
expect((req.updates || []).every((r) => r.b_id === undefined)).toBe(true)
|
||
})
|
||
|
||
it('与基线完全一致时不产生请求', () => {
|
||
const originalAll = [
|
||
...originalFlat,
|
||
{ b_key: 'module.demo', b_locale: 'zh-CN', b_value: '演示模块' },
|
||
{ b_key: 'field.demo.name', b_locale: 'zh-CN', b_value: '姓名' },
|
||
]
|
||
const items = collectManagedI18nItems({
|
||
module: demoModule(),
|
||
fields: [demoField()],
|
||
schemas: [],
|
||
})
|
||
const rows = reconcileI18nRows(buildI18nPivot(originalAll), items, {
|
||
defaultLocale,
|
||
languages,
|
||
})
|
||
const req = buildI18nChangeRequest({
|
||
draftRows: rows,
|
||
originalFlat: originalAll,
|
||
languages,
|
||
})
|
||
expect(req).toBeNull()
|
||
})
|
||
})
|