/** * 删除 SQL 规则面板(module-management/ModuleRulePanel): * - 加载回填只补界面派生列(_rowKey / _labelText),业务列不变(不置脏); * - 列表识别:优先提示文案,缺文案退回 SQL 片段; * - 保存前校验:必须有主键、SQL 含 :ids 且为只读查询、必须填提示文案; * - 新增规则:取号后打开弹窗,确定时才落草稿。 */ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import { mount, 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' import { IDS_PLACEHOLDER } from '@/views/module/module-management/ruleUtils' 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 }, }) }) /** 库中已有的一条删除 SQL 规则 */ const sqlRow = { b_id: '1900000000000000001', b_module_id: 'order', b_sql: `select b_id from bill where b_id in (${IDS_PLACEHOLDER}) and b_settled = 1`, b_message: '单据已销账,不能删除', b_canuse: 1, b_xh: 10, } async function mountPanel({ rules = [] } = {}) { const pinia = createPinia() setActivePinia(pinia) const wrapper = mount(ModuleRulePanel, { props: { moduleId: 'order', 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('删除 SQL 规则面板', () => { it('加载回填只补界面派生列,业务列不变', async () => { const wrapper = await mountPanel({ rules: [{ ...sqlRow }] }) const next = lastRules(wrapper) expect(next).toHaveLength(1) expect(next[0]._rowKey).toBeTruthy() expect(next[0]._labelText).toBe('单据已销账,不能删除') // 剥掉 _ 前缀的内部键后与库中行完全一致(不置脏) expect(stripInternalKeys(next[0])).toEqual(sqlRow) }) it('识别文案:缺提示文案时退回 SQL 片段', async () => { const wrapper = await mountPanel({ rules: [{ ...sqlRow, b_message: null }], }) const next = lastRules(wrapper) expect(next[0]._labelText).toContain('select b_id from bill') }) it('校验:主键、:ids 标记、只读查询、提示文案都要满足', async () => { const noId = { ...sqlRow, b_id: '' } const noPlaceholder = { ...sqlRow, b_id: '1900000000000000002', b_sql: 'select b_id from bill' } const notReadOnly = { ...sqlRow, b_id: '1900000000000000003', b_sql: `delete from bill where b_id in (${IDS_PLACEHOLDER})`, } const noMessage = { ...sqlRow, b_id: '1900000000000000004', b_message: '', } const wrapper = await mountPanel({ rules: [noId, noPlaceholder, notReadOnly, noMessage] }) const errors = wrapper.vm.validate() expect(errors).toContain('第 1 条规则:缺少主键(请重新新增)') expect(errors).toContain('第 2 条规则:规则 SQL 必须包含 :ids 标记') expect(errors).toContain('第 3 条规则:规则 SQL 必须是只读查询(以 select 或 with 开头)') expect(errors).toContain('第 4 条规则:请填写提示文案') }) 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.editingRow.b_module_id).toBe('order') expect(wrapper.vm.ruleModalOpen).toBe(true) // 未确定前不进列表 expect(lastRules(wrapper)).toBe(null) wrapper.vm.onRuleOk({ ...wrapper.vm.editingRow, b_sql: 'select 1 as b_id' }) await nextTick() expect(lastRules(wrapper)).toHaveLength(1) expect(lastRules(wrapper)[0].b_id).toBe('1900000000000000009') }) })