- useModuleConfig 新增 dirtySections(逐段 JSON.stringify 对比),hasModuleChanges 改为其派生;先以整树对比语义为基准写等价断言(含「加载补全默认行即脏」的 既有怪癖),新 spec module-dirty-sections 7 用例全绿 - index.vue #label 插槽给脏 tab 渲染 6px 圆点(title/aria-label,不只靠颜色), ui/Tabs 组件零改动;groups 由列表/表单两面板共享编辑,两 tab 联动标脏 - 计划文档 docs/module-management-tab-status-plan.md(横排保留、不做分组为已决策)
139 lines
5.2 KiB
JavaScript
139 lines
5.2 KiB
JavaScript
/**
|
||
* 分 section 脏检查测试:守护 hasModuleChanges 的判定语义。
|
||
* 背景见 docs/module-management-tab-status-plan.md 阶段一:
|
||
* - 加载时自动补全的默认列表行只写 drafts 不写 originals(useModuleConfig.loadModuleConfig),
|
||
* 此时视为「脏」是既有行为,分 section 对比必须原样保留,不得“修复”;
|
||
* - hasModuleChanges 从整树 JSON.stringify 对比改为 dirtySections 派生,
|
||
* 本 spec 以判定结果等价为验收基准。
|
||
*/
|
||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||
import { ref } from 'vue'
|
||
import { useModuleConfig } from '@/views/system/module-management/composables/useModuleConfig'
|
||
|
||
vi.mock('@/services/api', () => ({
|
||
loadDataApi: vi.fn(),
|
||
describeApi: vi.fn(),
|
||
}))
|
||
vi.mock('@/components/ui', () => ({
|
||
Message: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() },
|
||
}))
|
||
|
||
beforeEach(() => {
|
||
vi.clearAllMocks()
|
||
})
|
||
|
||
/** 完整的 8 段草稿结构(与 loadModuleConfig 构造的 original 同形) */
|
||
function baseConfig(over = {}) {
|
||
const config = {
|
||
module: { b_id: '1', b_code: 'demo', b_name: '演示模块', b_module_type: 'data' },
|
||
fields: [{ b_id: '10', b_field: 'amount', b_name: '金额', b_canuse: 1 }],
|
||
groups: [],
|
||
listConfig: [],
|
||
editConfig: [],
|
||
queryConfig: [],
|
||
autoCode: [],
|
||
powers: [],
|
||
}
|
||
return Object.assign(config, over)
|
||
}
|
||
|
||
function setup({ drafts, originals }) {
|
||
const draftsRef = ref(drafts)
|
||
const originalsRef = ref(originals)
|
||
const deps = {
|
||
moduleSelectedId: ref('1'),
|
||
moduleTreeData: ref([]),
|
||
findModuleNode: () => null,
|
||
selectedModule: ref({ b_id: '1', b_code: 'demo', b_name: '演示模块' }),
|
||
busy: ref(false),
|
||
configLoading: ref(false),
|
||
configSyncing: ref(false),
|
||
activeSection: ref('basic'),
|
||
loadedModuleId: ref('1'),
|
||
originals: originalsRef,
|
||
drafts: draftsRef,
|
||
skipModuleLoad: { value: false },
|
||
showConfirm: vi.fn(),
|
||
switchConfirm: ref(null),
|
||
i18nPanelRef: ref(null),
|
||
}
|
||
return { ...useModuleConfig(deps), originalsRef, draftsRef }
|
||
}
|
||
|
||
describe('useModuleConfig 脏检查', () => {
|
||
it('未修改时 hasModuleChanges 为 false', () => {
|
||
const originals = baseConfig()
|
||
const { hasModuleChanges } = setup({ drafts: JSON.parse(JSON.stringify(originals)), originals })
|
||
expect(hasModuleChanges.value).toBe(false)
|
||
})
|
||
|
||
it('无草稿时 hasModuleChanges 为 false', () => {
|
||
const { hasModuleChanges } = setup({ drafts: null, originals: null })
|
||
expect(hasModuleChanges.value).toBe(false)
|
||
})
|
||
|
||
it('任一 section 修改即 hasModuleChanges 为 true', () => {
|
||
const originals = baseConfig()
|
||
for (const key of ['module', 'fields', 'groups', 'listConfig', 'editConfig', 'queryConfig', 'autoCode', 'powers']) {
|
||
const drafts = JSON.parse(JSON.stringify(originals))
|
||
if (key === 'module') drafts.module.b_name = '改名'
|
||
else drafts[key] = [...drafts[key], { b_id: 'new' }]
|
||
const { hasModuleChanges } = setup({ drafts, originals })
|
||
expect(hasModuleChanges.value, `修改 ${key} 应判定为脏`).toBe(true)
|
||
}
|
||
})
|
||
|
||
it('加载时补全的默认列表行(只写 drafts)视为脏——既有行为', () => {
|
||
const originals = baseConfig()
|
||
const drafts = JSON.parse(JSON.stringify(originals))
|
||
drafts.listConfig = [{ b_id: 'tmp1', b_field_id: '10', b_visible: 1, b_xh: 10 }]
|
||
const { hasModuleChanges } = setup({ drafts, originals })
|
||
expect(hasModuleChanges.value).toBe(true)
|
||
})
|
||
|
||
it('查询模块缺失的 section(undefined)两侧一致时不脏,单侧出现即脏', () => {
|
||
const originals = {
|
||
module: { b_id: '2', b_code: 'q', b_module_type: 'query' },
|
||
fields: [],
|
||
groups: [],
|
||
listConfig: [],
|
||
editConfig: undefined,
|
||
queryConfig: [],
|
||
autoCode: undefined,
|
||
powers: [],
|
||
}
|
||
const same = setup({ drafts: JSON.parse(JSON.stringify(originals)), originals })
|
||
expect(same.hasModuleChanges.value).toBe(false)
|
||
|
||
const drafts = JSON.parse(JSON.stringify(originals))
|
||
drafts.editConfig = []
|
||
const diff = setup({ drafts, originals })
|
||
expect(diff.hasModuleChanges.value).toBe(true)
|
||
})
|
||
|
||
it('dirtySections 精确指出被修改的 section', () => {
|
||
const originals = baseConfig()
|
||
const drafts = JSON.parse(JSON.stringify(originals))
|
||
drafts.module.b_name = '改名'
|
||
drafts.queryConfig = [{ b_id: 'q1' }]
|
||
const { dirtySections } = setup({ drafts, originals })
|
||
expect(dirtySections.value.has('module')).toBe(true)
|
||
expect(dirtySections.value.has('queryConfig')).toBe(true)
|
||
expect(dirtySections.value.has('fields')).toBe(false)
|
||
expect(dirtySections.value.size).toBe(2)
|
||
})
|
||
|
||
it('保存后整体替换 originals/drafts 为同一份深拷贝,脏状态清空', () => {
|
||
const originals = baseConfig()
|
||
const drafts = JSON.parse(JSON.stringify(originals))
|
||
drafts.module.b_name = '改名'
|
||
const api = setup({ drafts, originals })
|
||
expect(api.hasModuleChanges.value).toBe(true)
|
||
// 模拟 useModuleSave 保存后的整体 cloneData 换引用
|
||
const saved = JSON.parse(JSON.stringify(drafts))
|
||
api.originalsRef.value = saved
|
||
api.draftsRef.value = JSON.parse(JSON.stringify(saved))
|
||
expect(api.hasModuleChanges.value).toBe(false)
|
||
})
|
||
})
|