Files
workspace/code/fms/fms-vue/tests/unit/module-i18n-reconcile.spec.js
2026-09-17 16:58:40 +08:00

230 lines
7.8 KiB
JavaScript
Raw Permalink 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.
/**
* 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()
})
})