20260919211702
This commit is contained in:
1 parent
f56e70b050
commit
f4bfc1ddfc
97 files changed
+6059
-1712
No files matched your search
@@ -2,12 +2,13 @@
|
||||
* 模块关联面板(s_relation):
|
||||
* - 加载回填行只补本地行键(_rowKey),不得改动业务列——否则打开模块即提示「未保存」;
|
||||
* - 新增关联:追加「源模块 = 当前模块」的空行,主键四列留待用户填写;
|
||||
* 新建关系默认关联删除处理为 restrict(设计 §3.1);
|
||||
* - 换目标模块:原目标字段必然失效,需清空并按目标模块加载字段选项;
|
||||
* - 保存前校验:四列非空、关系类型合法、四列组合不重复(空列会撞行身份,落库即主键冲突)。
|
||||
* - 关联删除处理三值对多对一同样开放:类型切成多对一时不得重置 b_on_delete(设计 §3.2);
|
||||
* - 保存前校验:四列非空、关系类型合法、删除处置合法、四列组合不重复。
|
||||
*/
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises } from '@vue/test-utils'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { nextTick } from 'vue'
|
||||
import ModuleRelationPanel from '@/views/module/module-management/ModuleRelationPanel.vue'
|
||||
@@ -51,13 +52,13 @@ const sourceRow = {
|
||||
b_target_module_id: 'order_item',
|
||||
b_target_field: 'b_order_id',
|
||||
b_relation_type: 'one_to_many',
|
||||
b_on_delete: 'restrict',
|
||||
b_canuse: 1,
|
||||
b_xh: 10,
|
||||
}
|
||||
|
||||
async function mountPanel(modelValue = [], { refRules = [] } = {}) {
|
||||
async function mountPanel(modelValue = []) {
|
||||
const pinia = createPinia()
|
||||
pinia.install({ provide() {}, config: { globalProperties: {} } })
|
||||
setActivePinia(pinia)
|
||||
const wrapper = mount(ModuleRelationPanel, {
|
||||
props: {
|
||||
@@ -67,7 +68,6 @@ async function mountPanel(modelValue = [], { refRules = [] } = {}) {
|
||||
reverseRows: [],
|
||||
loading: false,
|
||||
modelValue,
|
||||
refRules,
|
||||
},
|
||||
global: { plugins: [pinia] },
|
||||
attachTo: document.body,
|
||||
@@ -82,23 +82,6 @@ const lastRelations = (wrapper) => {
|
||||
return events.length ? events[events.length - 1][0] : null
|
||||
}
|
||||
|
||||
const lastRefRules = (wrapper) => {
|
||||
const events = wrapper.emitted('update:refRules') || []
|
||||
return events.length ? events[events.length - 1][0] : null
|
||||
}
|
||||
|
||||
/** 已勾选引用规则的关系行(refRules 里挂着对应的 relation 规则) */
|
||||
const REF_SCOPE_ID = 'order|b_id|order_item|b_order_id'
|
||||
const refRuleRow = {
|
||||
b_id: '1900000000000000001',
|
||||
b_scope_type: 'relation',
|
||||
b_scope_id: REF_SCOPE_ID,
|
||||
b_hook: 'delete.pre',
|
||||
b_predicate: null,
|
||||
b_canuse: 1,
|
||||
b_xh: 10,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
loadDataApi.mockReset()
|
||||
@@ -119,7 +102,7 @@ describe('模块关联面板', () => {
|
||||
expect(stripInternalKeys(next[0])).toEqual(sourceRow)
|
||||
})
|
||||
|
||||
it('新增关联:追加源模块为当前模块的空行并进入编辑', async () => {
|
||||
it('新增关联:追加源模块为当前模块的空行,默认关联删除处理为 restrict', async () => {
|
||||
const wrapper = await mountPanel([{ ...sourceRow }])
|
||||
await wrapper.vm.addRow()
|
||||
await nextTick()
|
||||
@@ -132,6 +115,7 @@ describe('模块关联面板', () => {
|
||||
b_target_module_id: null,
|
||||
b_target_field: null,
|
||||
b_relation_type: 'many_to_one',
|
||||
b_on_delete: 'restrict',
|
||||
b_canuse: 1,
|
||||
b_xh: 20,
|
||||
})
|
||||
@@ -216,88 +200,31 @@ describe('模块关联面板', () => {
|
||||
expect(wrapper.vm.selectedRowKeys).toEqual([])
|
||||
})
|
||||
|
||||
it('删除连带:加载回填不置脏,多对一自动清回不连带', async () => {
|
||||
const cascadeRow = { ...sourceRow, b_on_delete: 'cascade' }
|
||||
it('关联删除处理:加载回填不置脏,多对一不再被重置(允许 cascade)', async () => {
|
||||
const cascadeRow = { ...sourceRow, b_relation_type: 'many_to_one', b_on_delete: 'cascade' }
|
||||
// 传入副本:面板会往行对象上补 _ 前缀派生列
|
||||
const wrapper = await mountPanel([{ ...cascadeRow }])
|
||||
const row = lastRelations(wrapper)[0]
|
||||
expect(stripInternalKeys(row)).toEqual(cascadeRow)
|
||||
|
||||
// 类型改成多对一:连带策略不再成立,自动清回 none 并提示
|
||||
// 类型在多对一与一对多之间切换:处置值必须原样保留,不再被硬编码清回 none
|
||||
row.b_relation_type = 'many_to_one'
|
||||
wrapper.vm.onCellChange(row, { dataIndex: 'b_relation_type' })
|
||||
await nextTick()
|
||||
|
||||
expect(row.b_on_delete).toBe('none')
|
||||
expect(Message.warning).toHaveBeenCalledWith(
|
||||
'多对一关系不支持连带删除/连带归档,已重置为不连带',
|
||||
)
|
||||
expect(row.b_on_delete).toBe('cascade')
|
||||
expect(Message.warning).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('校验:多对一上配连带删除/归档被拦,取值非法被拦', async () => {
|
||||
it('校验:多对一配 cascade 合法,仅取值非法被拦', async () => {
|
||||
const wrapper = await mountPanel([
|
||||
{ ...sourceRow, b_relation_type: 'many_to_one', b_on_delete: 'cascade' },
|
||||
{ ...sourceRow, b_target_field: 'b_other', b_on_delete: 'remove' },
|
||||
])
|
||||
const errors = wrapper.vm.validate()
|
||||
|
||||
expect(errors).toContain('第 1 行:多对一关系不支持连带删除/连带归档')
|
||||
expect(errors).toContain('第 2 行:删除连带取值不合法')
|
||||
})
|
||||
|
||||
it('引用规则:勾选取号建行、取消删行,回填按业务键还原勾选态', async () => {
|
||||
const wrapper = await mountPanel([{ ...sourceRow }], { refRules: [] })
|
||||
const row = wrapper.vm.relations[0]
|
||||
|
||||
row._hasRefRule = 1
|
||||
wrapper.vm.onCellChange(row, { dataIndex: '_hasRefRule' })
|
||||
await flushPromises()
|
||||
await nextTick()
|
||||
|
||||
const added = lastRefRules(wrapper)
|
||||
expect(added).toHaveLength(1)
|
||||
expect(added[0]).toMatchObject({
|
||||
b_id: '1900000000000000009',
|
||||
b_scope_type: 'relation',
|
||||
b_scope_id: REF_SCOPE_ID,
|
||||
b_predicate: null,
|
||||
})
|
||||
|
||||
// 已有规则时加载:勾选态由业务键还原(不置脏)
|
||||
const withRule = await mountPanel([{ ...sourceRow }], { refRules: [{ ...refRuleRow }] })
|
||||
expect(stripInternalKeys(withRule.vm.relations[0])).toEqual(sourceRow)
|
||||
expect(withRule.vm.relations[0]._hasRefRule).toBe(1)
|
||||
|
||||
// 取消勾选:规则行被移除
|
||||
const target = withRule.vm.relations[0]
|
||||
target._hasRefRule = 0
|
||||
withRule.vm.onCellChange(target, { dataIndex: '_hasRefRule' })
|
||||
await nextTick()
|
||||
expect(lastRefRules(withRule)).toEqual([])
|
||||
})
|
||||
|
||||
it('改关系主键:自动取消引用保护并删掉旧键规则,避免库中残留孤儿规则', async () => {
|
||||
const wrapper = await mountPanel([{ ...sourceRow }], { refRules: [{ ...refRuleRow }] })
|
||||
const row = wrapper.vm.relations[0]
|
||||
expect(row._hasRefRule).toBe(1)
|
||||
|
||||
row.b_target_module_id = 'legacy'
|
||||
wrapper.vm.onCellChange(row, { dataIndex: 'b_target_module_id' })
|
||||
await nextTick()
|
||||
|
||||
expect(row._hasRefRule).toBe(0)
|
||||
expect(lastRefRules(wrapper)).toEqual([])
|
||||
expect(Message.info).toHaveBeenCalledWith(
|
||||
'关系主键已变化,「有引用时拒绝删除」已取消,请重新勾选',
|
||||
)
|
||||
})
|
||||
|
||||
it('删掉关系行时,挂在它上面的引用规则一并移除', async () => {
|
||||
const wrapper = await mountPanel([{ ...sourceRow }], { refRules: [{ ...refRuleRow }] })
|
||||
wrapper.vm.removeRow(wrapper.vm.relations[0])
|
||||
await nextTick()
|
||||
|
||||
expect(lastRefRules(wrapper)).toEqual([])
|
||||
expect(lastRelations(wrapper)).toHaveLength(0)
|
||||
// 多对一 + cascade 是允许的场景(如「明细 → 主单」删主单级联删明细)
|
||||
expect(errors.some((item) => item.includes('第 1 行'))).toBe(false)
|
||||
expect(errors).toContain('第 2 行:关联删除处理取值不合法')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user