20260917212907

This commit is contained in:
oneao committed 2026-09-17 21:29:08 +08:00
1 parent 6fa3d8535e
commit 5cbf745bbf
115 files changed
+4104 -85

No files matched your search

@@ -31,6 +31,21 @@ vi.mock('@/components/ui/message/message-manager.js', () => {
import { loadDataApi, saveObjectApi } from '@/services/api'
import { Message } from '@/components/ui/message/message-manager.js'
import { createRefRuleRow } from '@/views/module/module-management/ruleUtils'
/** 库中的一条关系:demo.b_name → other.b_name(一对多,源侧即当前模块) */
function relationRow() {
return {
b_source_module_id: 'demo',
b_source_field: 'b_name',
b_target_module_id: 'other',
b_target_field: 'b_name',
b_relation_type: 'one_to_many',
b_on_delete: 'none',
b_canuse: 1,
b_xh: 10,
}
}
function moduleRow(id, name) {
return {
@@ -94,6 +109,8 @@ function buildDb() {
s_module_schema: schemas,
s_autocode: [],
s_power: [],
s_relation: [],
s_rule: [],
s_i18n: [{ b_key: 'module.demo', b_locale: 'en-US', b_value: 'Demo module' }],
}
}
@@ -105,7 +122,7 @@ beforeEach(() => {
savedRequests = []
loadDataApi.mockReset()
loadDataApi.mockImplementation((view) => {
loadDataApi.mockImplementation((view, condition) => {
if (view === 's_i18n_type')
return Promise.resolve({
data: [
@@ -113,6 +130,20 @@ beforeEach(() => {
{ b_id: 'en-US', b_name: 'English', b_default: 0 },
],
})
// s_rule 的 scope 条件是两列(模块级 / 关系级)拼在同一个查询条件里,
// 这里按条件过滤,模拟后端 search_condition 的真实行为
if (view === 's_rule' && typeof condition === 'string') {
if (condition.includes("b_scope_type = 'module'")) {
return Promise.resolve({
data: rowsOf(view).filter((row) => row.b_scope_type === 'module'),
})
}
if (condition.includes("b_scope_type = 'relation'")) {
return Promise.resolve({
data: rowsOf(view).filter((row) => row.b_scope_type === 'relation'),
})
}
}
return Promise.resolve({ data: rowsOf(view) })
})
@@ -367,4 +398,225 @@ describe('模块管理:保存后的未保存判定', () => {
),
).toBe(true)
})
it('模块关联:加载拆分源/目标两侧,保存只提交源侧行(四列复合主键)', async () => {
db.s_relation = [
{
b_source_module_id: 'demo',
b_source_field: 'b_name',
b_target_module_id: 'other',
b_target_field: 'b_name',
b_relation_type: 'one_to_many',
b_canuse: 1,
b_xh: 10,
},
{
b_source_module_id: 'other',
b_source_field: 'b_name',
b_target_module_id: 'demo',
b_target_field: 'b_name',
b_relation_type: 'many_to_one',
b_canuse: 1,
b_xh: 20,
},
]
const wrapper = await mountManagement()
await flushPromises()
wrapper.vm.moduleSelectedId = 'demo'
await flushPromises()
await flushPromises()
// 源侧进可编辑草稿,目标侧只读展示;打开即干净(本地行键不得算作改动)
expect(wrapper.vm.relations).toHaveLength(1)
expect(wrapper.vm.reverseRelations).toHaveLength(1)
expect(String(wrapper.vm.reverseRelations[0].b_source_module_id)).toBe('other')
expect(wrapper.vm.hasUnsavedChanges()).toBe(false)
wrapper.vm.relations[0].b_canuse = 0
expect(wrapper.vm.hasUnsavedChanges()).toBe(true)
await wrapper.vm.handleSave()
await settleConfigReload(wrapper)
const relationReq = savedRequests.find((request) => request.table === 's_relation')
expect(relationReq, '关联变更应提交 s_relation').toBeTruthy()
expect(relationReq.key_field).toBe(
'b_source_module_id,b_source_field,b_target_module_id,b_target_field',
)
expect(relationReq.updates).toEqual([
{
b_source_module_id: 'demo',
b_source_field: 'b_name',
b_target_module_id: 'other',
b_target_field: 'b_name',
b_relation_type: 'one_to_many',
b_canuse: 0,
b_xh: 10,
},
])
// 反向行不属于本模块草稿,保存不得把它算作删除
expect(relationReq.deletes ?? []).toEqual([])
expect(wrapper.vm.hasUnsavedChanges()).toBe(false)
})
it('删除模块应一并清理双向模块关联(s_relation)', async () => {
const wrapper = await mountManagement()
await flushPromises()
wrapper.vm.moduleSelectedId = 'demo'
await flushPromises()
await flushPromises()
wrapper.vm.deleteModule('demo')
await flushPromises()
await wrapper.vm.onConfirmOk()
await flushPromises()
const relationReqs = savedRequests.filter((request) => request.table === 's_relation')
expect(relationReqs).toHaveLength(2)
expect(relationReqs).toEqual([
{
table: 's_relation',
key_field: 'b_source_module_id',
deletes: [{ b_source_module_id: 'demo' }],
},
{
table: 's_relation',
key_field: 'b_target_module_id',
deletes: [{ b_target_module_id: 'demo' }],
},
])
})
it('删除规则:模块级规则加载不脏,只提交变更(雪花主键)', async () => {
db.s_relation = [relationRow()]
db.s_rule = [
{
b_id: '1900000000000000001',
b_scope_type: 'module',
b_scope_id: 'demo',
b_hook: 'delete.pre',
b_predicate: JSON.stringify({
kind: 'dsl',
expr: {
type: 'group',
children: [
{ type: 'condition', field: 'b_name', operator: 'like', value: 'a', join: 'and' },
],
},
}),
b_message: null,
b_canuse: 1,
b_xh: 10,
},
{
b_id: '1900000000000000002',
b_scope_type: 'relation',
b_scope_id: 'demo|b_name|other|b_name',
b_hook: 'delete.pre',
b_predicate: null,
b_message: null,
b_canuse: 1,
b_xh: 10,
},
]
const wrapper = await mountManagement()
await flushPromises()
wrapper.vm.moduleSelectedId = 'demo'
await flushPromises()
await flushPromises()
// 模块级规则进 rules,关系级进 refRules;打开即干净
expect(wrapper.vm.rules).toHaveLength(1)
expect(wrapper.vm.refRules).toHaveLength(1)
expect(wrapper.vm.hasUnsavedChanges()).toBe(false)
wrapper.vm.rules[0].b_canuse = 0
expect(wrapper.vm.hasUnsavedChanges()).toBe(true)
await wrapper.vm.handleSave()
await settleConfigReload(wrapper)
const ruleReq = savedRequests.find((request) => request.table === 's_rule')
expect(ruleReq, '规则变更应提交 s_rule').toBeTruthy()
expect(ruleReq.key_field).toBe('b_id')
expect(ruleReq.updates?.[0]).toMatchObject({
b_id: '1900000000000000001',
b_scope_type: 'module',
b_scope_id: 'demo',
b_canuse: 0,
})
// 关系未变:不产生 s_relation 请求
expect(savedRequests.some((request) => request.table === 's_relation')).toBe(false)
expect(wrapper.vm.hasUnsavedChanges()).toBe(false)
})
it('关联面板勾选引用保护:保存时新增一条 relation 范围的规则', async () => {
db.s_relation = [relationRow()]
const wrapper = await mountManagement()
await flushPromises()
wrapper.vm.moduleSelectedId = 'demo'
await flushPromises()
await flushPromises()
expect(wrapper.vm.refRules).toEqual([])
wrapper.vm.refRules = [createRefRuleRow('demo|b_name|other|b_name', '1900000000000000003')]
expect(wrapper.vm.hasUnsavedChanges()).toBe(true)
await wrapper.vm.handleSave()
await settleConfigReload(wrapper)
const refReq = savedRequests.find((request) => request.table === 's_rule')
expect(refReq?.key_field).toBe('b_id')
expect(refReq?.inserts?.[0]).toMatchObject({
b_id: '1900000000000000003',
b_scope_type: 'relation',
b_scope_id: 'demo|b_name|other|b_name',
b_predicate: null,
b_hook: 'delete.pre',
})
expect(wrapper.vm.hasUnsavedChanges()).toBe(false)
})
it('删除模块应一并清理模块级与关系级删除规则(s_rule)', async () => {
db.s_relation = [relationRow()]
db.s_rule = [
{
b_id: '1900000000000000001',
b_scope_type: 'module',
b_scope_id: 'demo',
b_hook: 'delete.pre',
b_predicate: 'select 1 as b_id',
},
{
b_id: '1900000000000000002',
b_scope_type: 'relation',
b_scope_id: 'demo|b_name|other|b_name',
b_hook: 'delete.pre',
b_predicate: null,
},
]
const wrapper = await mountManagement()
await flushPromises()
wrapper.vm.moduleSelectedId = 'demo'
await flushPromises()
await flushPromises()
wrapper.vm.deleteModule('demo')
await flushPromises()
await wrapper.vm.onConfirmOk()
await flushPromises()
const ruleReqs = savedRequests.filter((request) => request.table === 's_rule')
const moduleReq = ruleReqs.find(
(request) => request.deletes?.[0]?.b_scope_type === 'module' && request.deletes.length === 1,
)
expect(moduleReq?.deletes).toEqual([{ b_scope_type: 'module', b_scope_id: 'demo' }])
const relationReq = ruleReqs.find((request) =>
request.deletes?.some((row) => row.b_scope_type === 'relation'),
)
expect(relationReq?.deletes).toEqual([
{ b_scope_type: 'relation', b_scope_id: 'demo|b_name|other|b_name' },
])
})
})