/** * i18n 统一维护纯函数测试: * 覆盖 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' function demoModule(over = {}) { return { b_id: '9', b_code: 'demo', b_module_type: 'data', b_name: '演示模块', b_i18n: null, ...over, } } describe('collectManagedI18nItems 受管键收集', () => { it('收集模块名与字段名的受管键', () => { const items = collectManagedI18nItems({ module: demoModule(), fields: [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }], groups: [], }) 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('模块编码为空时不产出受管键(新建未填编码)', () => { const items = collectManagedI18nItems({ module: demoModule({ b_code: '' }), fields: [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }], groups: [], }) expect(items).toEqual([]) }) }) describe('buildI18nPivot / reconcileI18nRows', () => { it('扁平行转透视后 reconcile 补齐受管行并跟随默认语言', () => { const flat = [{ b_id: '100', b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' }] const pivot = buildI18nPivot(flat) const items = collectManagedI18nItems({ module: demoModule(), fields: [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }], groups: [], }) 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 fields = [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }] const flat = [{ b_id: '100', b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' }] let rows = reconcileI18nRows( buildI18nPivot(flat), collectManagedI18nItems({ module: demoModule(), fields, groups: [] }), { defaultLocale, languages }, ) // 改名后再次 reconcile rows = reconcileI18nRows( rows, collectManagedI18nItems({ module: demoModule({ b_name: '演示模块V2' }), fields: [{ ...fields[0], b_name: '姓名V2' }], groups: [], }), { 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 field = { b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null } const items = collectManagedI18nItems({ module: demoModule(), fields: [field], groups: [] }) 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_id: '100', b_key: 'field.demo.name', b_locale: 'en-US', b_value: 'Name' }, ] function draftRows() { const items = collectManagedI18nItems({ module: demoModule(), fields: [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }], groups: [], }) return reconcileI18nRows(buildI18nPivot(originalFlat), items, { defaultLocale, languages }) } it('首存新增受管默认语言 cell,已存在值不重复插入', () => { const req = buildI18nChangeRequest({ draftRows: draftRows(), originalFlat, moduleId: '9', languages, }) expect(req).not.toBeNull() 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']) // 新增行 b_id 为负数临时 id(保存编排统一换真实主键) expect((req.inserts || []).every((r) => /^-\d+$/.test(String(r.b_id)))).toBe(true) // en-US 有主键且未变化 → 不产生 update expect(req.updates || []).toHaveLength(0) }) it('修改语言值只产生对应 cell 的 update', () => { const rows = draftRows() rows.find((r) => r.b_key === 'field.demo.name')['en-US'] = 'NewName' const req = buildI18nChangeRequest({ draftRows: rows, originalFlat, moduleId: '9', languages, }) expect(req.updates.some((r) => r.b_id === '100' && r.b_value === 'NewName')).toBe(true) }) it('与基线完全一致时不产生请求', () => { const originalAll = [ ...originalFlat, { b_id: '101', b_key: 'module.demo', b_locale: 'zh-CN', b_value: '演示模块' }, { b_id: '102', b_key: 'field.demo.name', b_locale: 'zh-CN', b_value: '姓名' }, ] const items = collectManagedI18nItems({ module: demoModule(), fields: [{ b_id: '1', b_field: 'name', b_name: '姓名', b_i18n: null }], groups: [], }) const rows = reconcileI18nRows(buildI18nPivot(originalAll), items, { defaultLocale, languages, }) const req = buildI18nChangeRequest({ draftRows: rows, originalFlat: originalAll, moduleId: '9', languages, }) expect(req).toBeNull() }) })