Files
workspace/code/fms/fms-vue/tests/unit/module-rule-panel.spec.js
T
2026-09-17 21:29:08 +08:00

192 lines
6.7 KiB
JavaScript
Raw 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.
/**
* 删除规则面板(module-management/ModuleRulePanel):
* - 加载回填只补界面派生列(_rowKey / _typeText / _labelText),业务列不变(不置脏);
* - 列表识别:勾条件显示条件中文摘要,写 SQL 显示提示文案;
* - 保存前校验:勾条件要有条件、写 SQL 要有 SQL 与提示文案;
* - 其他模块在本模块上配的引用规则只读展示,可跳转到源模块。
*/
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import ModuleRulePanel from '@/views/module/module-management/ModuleRulePanel.vue'
import { stripInternalKeys } from '@/utils/dataChanges'
vi.mock('@/services/api', () => ({
loadDataApi: vi.fn(),
nextIdApi: vi.fn(() => Promise.resolve({ data: '1900000000000000009', code: 0 })),
}))
vi.mock('@/components/ui/message/message-manager.js', () => {
const m = { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() }
return { Message: m, default: m }
})
// jsdom 无布局:StkTable 在 clientWidth 为 0 时启用虚拟列裁剪,补成宽容器
beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
configurable: true,
get() {
return 5000
},
})
})
const FIELDS = [
{ b_field: 'b_status', b_name: '状态', b_type: 'select', b_canuse: 1 },
{ b_field: 'b_settled', b_name: '已销账', b_type: 'checkbox', b_canuse: 1 },
]
const MODULES = [
{ b_id: 'order', b_name: '订单', b_canuse: 1 },
{ b_id: 'order_item', b_name: '订单明细', b_canuse: 1 },
]
/** 库中已有的一条勾条件规则:状态等于 audit */
const dslRow = {
b_id: '1900000000000000001',
b_scope_type: 'module',
b_scope_id: 'order',
b_hook: 'delete.pre',
b_predicate: JSON.stringify({
kind: 'dsl',
expr: {
type: 'group',
children: [
{ type: 'condition', field: 'b_status', operator: 'eq', value: 'audit', join: 'and' },
],
},
}),
b_message: null,
b_canuse: 1,
b_xh: 10,
}
async function mountPanel({ rules = [], refRulesReverse = [] } = {}) {
const pinia = createPinia()
pinia.install({ provide() {}, config: { globalProperties: {} } })
setActivePinia(pinia)
const wrapper = mount(ModuleRulePanel, {
props: {
moduleId: 'order',
fields: FIELDS,
querySchema: {},
refRulesReverse,
modules: MODULES,
loading: false,
modelValue: rules,
},
global: { plugins: [pinia] },
attachTo: document.body,
})
await flushPromises()
await nextTick()
return wrapper
}
const lastRules = (wrapper) => {
const events = wrapper.emitted('update:modelValue') || []
return events.length ? events[events.length - 1][0] : null
}
beforeEach(() => {
document.body.innerHTML = ''
})
describe('删除规则面板', () => {
it('加载回填只补界面派生列,业务列不变', async () => {
const wrapper = await mountPanel({ rules: [{ ...dslRow }] })
const next = lastRules(wrapper)
expect(next).toHaveLength(1)
expect(next[0]._rowKey).toBeTruthy()
expect(next[0]._typeText).toBe('条件')
expect(next[0]._labelText).toBe('状态 等于 audit')
// 剥掉 _ 前缀的内部键后与库中行完全一致(不置脏)
expect(stripInternalKeys(next[0])).toEqual(dslRow)
})
it('识别文案:写 SQL 的规则用提示文案当列表文案', async () => {
const sqlRow = {
...dslRow,
b_id: '1900000000000000002',
b_predicate: 'select b_id from bill where b_settled = 1',
b_message: '单据已销账,不能删除',
}
const wrapper = await mountPanel({ rules: [sqlRow] })
const next = lastRules(wrapper)
expect(next[0]._typeText).toBe('SQL')
expect(next[0]._labelText).toBe('单据已销账,不能删除')
})
it('校验:勾条件必须真有条件,写 SQL 必须有 SQL 与提示文案', async () => {
const emptyDsl = {
...dslRow,
b_id: '1900000000000000003',
b_predicate: JSON.stringify({ kind: 'dsl', expr: { type: 'group', children: [] } }),
}
const sqlWithoutMessage = {
...dslRow,
b_id: '1900000000000000004',
b_predicate: 'select b_id from bill where b_id in (:ids)',
b_message: '',
}
const wrapper = await mountPanel({ rules: [emptyDsl, sqlWithoutMessage] })
const errors = wrapper.vm.validate()
expect(errors).toContain('第 1 条规则:至少配置一个判定条件')
expect(errors).toContain('第 2 条规则:写 SQL 的规则必须填写提示文案')
// 引用规则不该出现在模块级列表里;没有主键的行也要拦住(新增取号失败的情况)
const refWrapper = await mountPanel({ rules: [{ ...dslRow, b_predicate: null }] })
expect(refWrapper.vm.validate()).toContain('第 1 条规则:引用规则请到「模块关联」面板维护')
const noIdWrapper = await mountPanel({ rules: [{ ...dslRow, b_id: '' }] })
expect(noIdWrapper.vm.validate()).toContain('第 1 条规则:缺少主键(请重新新增)')
})
it('新增规则:取号后打开弹窗,确定时才落草稿', async () => {
const wrapper = await mountPanel()
await wrapper.vm.openRuleModal(null)
await flushPromises()
expect(wrapper.vm.editingIsNew).toBe(true)
expect(wrapper.vm.editingRow.b_id).toBe('1900000000000000009')
expect(wrapper.vm.ruleModalOpen).toBe(true)
// 未确定前不进列表
expect(lastRules(wrapper)).toBe(null)
wrapper.vm.onRuleOk({ ...wrapper.vm.editingRow, b_predicate: 'select 1 as b_id' })
await nextTick()
expect(lastRules(wrapper)).toHaveLength(1)
expect(lastRules(wrapper)[0].b_id).toBe('1900000000000000009')
})
it('只读区:把引用规则还原成模块名/字段列,并支持跳转到源模块', async () => {
const wrapper = await mountPanel({
refRulesReverse: [
{
b_id: '1900000000000000005',
b_scope_type: 'relation',
b_scope_id: 'order_item|b_order_id|order|b_id',
b_hook: 'delete.pre',
b_predicate: null,
b_canuse: 1,
b_xh: 10,
},
],
})
await nextTick()
expect(wrapper.text()).toContain('其他模块在本模块上配置的引用规则')
expect(wrapper.text()).toContain('订单明细(order_item)')
expect(wrapper.text()).toContain('订单(order)')
const viewButton = wrapper.findAll('button').find((button) => button.text().includes('查看'))
expect(viewButton, '只读区应有查看按钮').toBeTruthy()
await viewButton.trigger('click')
expect(wrapper.emitted('navigate')?.[0]).toEqual(['order_item'])
})
})