Files
workspace/code/fms/fms-vue/tests/unit/module-query-config-panel.spec.js
T
2026-09-24 17:25:48 +08:00

174 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):
* - 草稿与 query JSON 不是一一映射(缺省 component、老结构 conditions 转 SQL、已废弃的
* filterGroups / defaultViewCode 被清理),只加载(含父级重载换新对象)不得回写,
* 否则草稿与库中基线不一致 → 切树节点误报「未保存」,并静默改写库中数据;
* - 用户真实编辑(改分组 / 改页签条件 SQL)必须回写,且回写为 schemaVersion 3 形态。
*/
import { beforeAll, beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import ModuleQueryConfigPanel from '@/views/module/module-management/ModuleQueryConfigPanel.vue'
import FmsTable from '@/components/fms-table/FmsTable.vue'
// 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_amount', b_name: '金额', b_type: 'money', b_canuse: 1 },
]
/** 库中历史查询配置(schemaVersion 2:条件为结构化 conditions、默认页签在顶层、还有 filterGroups) */
const legacySchema = {
schemaVersion: 2,
defaultViewCode: 'pending',
views: [
{
code: 'pending',
label: '待处理',
order: 10,
showCount: true,
conditions: [{ field: 'b_status', operator: 'eq', value: '1' }],
},
],
keywordSearch: { placeholder: '', trigger: 'enter_or_debounce', fields: [] },
quickFilters: [],
filterGroups: [{ code: 'basic', label: '基础', order: 10, fields: ['b_status'] }],
queryFields: { b_status: { component: 'select', operator: 'eq' } },
}
async function mountPanel(modelValue) {
const pinia = createPinia()
pinia.install({ provide() {}, config: { globalProperties: {} } })
setActivePinia(pinia)
const wrapper = mount(ModuleQueryConfigPanel, {
props: { moduleId: 'demo', fields, modelValue, loading: false },
global: { plugins: [pinia] },
attachTo: document.body,
})
await nextTick()
return wrapper
}
const lastSchema = (wrapper) => {
const events = wrapper.emitted('update:modelValue') || []
return events.length ? events[events.length - 1][0] : null
}
/** 「工作队列页签」分节内的页签表格(四个分节同页平铺,按分节类名定位) */
function viewsTable(wrapper) {
return wrapper.find('.mm-qdesign__section--views').findComponent(FmsTable)
}
beforeEach(() => {
document.body.innerHTML = ''
})
describe('查询配置面板:加载不回写、编辑才回写', () => {
it('四个分节同页平铺(不用页签分组),各自带一张定高表格', async () => {
const wrapper = await mountPanel(legacySchema)
// 页签栏与页签面板都不应存在
expect(wrapper.findAll('.tabs-tab').length, '不应再有页签').toBe(0)
expect(wrapper.findAll('.tabs-pane').length, '不应再有页签面板').toBe(0)
const sections = wrapper.findAll('.mm-qdesign__section')
expect(sections.map((section) => section.find('.mm-qdesign__section-title strong').text())).toEqual(
['更多条件字段', '工作队列页签', '关键词字段', '快捷筛选字段'],
)
// 三个带表格的分节:表格容器有按行数算出的显式高度(不再靠 flex 撑满整屏)
for (const modifier of ['--fields', '--views', '--quick']) {
const tableBox = wrapper.find(`.mm-qdesign__section${modifier} .mm-qdesign__table`)
expect(tableBox.exists(), `${modifier} 分节应有表格容器`).toBe(true)
expect(tableBox.attributes('style'), `${modifier} 分节表格应有显式高度`).toMatch(
/height:\s*\d+px/,
)
expect(tableBox.findComponent(FmsTable).exists(), `${modifier} 分节应渲染表格`).toBe(true)
}
wrapper.unmount()
})
it('仅加载(含父级重载换新对象)不回写', async () => {
const wrapper = await mountPanel(legacySchema)
expect(lastSchema(wrapper)).toBe(null)
// 模拟父级重载:同一份历史 JSON 以新引用下发
await wrapper.setProps({ modelValue: JSON.parse(JSON.stringify(legacySchema)) })
await nextTick()
expect(lastSchema(wrapper)).toBe(null)
wrapper.unmount()
})
it('老结构条件在草稿里转成 SQL 展示,但加载本身不回写', async () => {
const wrapper = await mountPanel(legacySchema)
const rows = viewsTable(wrapper).props('dataSource')
expect(rows[0].conditionSql).toBe("[b_status] = N'1'")
expect(rows[0].isDefault).toBe(true)
expect(lastSchema(wrapper)).toBe(null)
wrapper.unmount()
})
it('改分组后回写为 schemaVersion 3(已废弃键被清理)', async () => {
const wrapper = await mountPanel(legacySchema)
const table = viewsTable(wrapper)
const rows = table.props('dataSource')
rows[0].group = '状态'
table.vm.$emit('change', rows[0], { dataIndex: 'group' })
await nextTick()
const next = lastSchema(wrapper)
expect(next).not.toBe(null)
expect(next.schemaVersion).toBe(3)
expect(next.views[0]).toMatchObject({
code: 'pending',
group: '状态',
isDefault: true,
conditionSql: "[b_status] = N'1'",
})
expect(next.filterGroups).toBeUndefined()
expect(next.defaultViewCode).toBeUndefined()
// 关键词只保留可搜索字段:旧的占位文案 / 触发方式随改写清理
expect(next.keywordSearch).toEqual({ fields: [] })
wrapper.unmount()
})
it('页签条件改为 SQL 后回写', async () => {
const wrapper = await mountPanel(legacySchema)
viewsTable(wrapper)
await wrapper.find('button.mm-qdesign__sql-cell').trigger('click')
await nextTick()
await wrapper.find('textarea').setValue("b_status = N'2'")
const buttons = wrapper.findAll('.fms-modal-footer button')
await buttons[buttons.length - 1].trigger('click')
await nextTick()
const next = lastSchema(wrapper)
expect(next).not.toBe(null)
expect(next.views[0].conditionSql).toBe("b_status = N'2'")
wrapper.unmount()
})
it('条件 SQL 含分号时不回写(拦截手误)', async () => {
const wrapper = await mountPanel(legacySchema)
viewsTable(wrapper)
await wrapper.find('button.mm-qdesign__sql-cell').trigger('click')
await nextTick()
await wrapper.find('textarea').setValue("b_status = N'2'; DELETE FROM x")
const buttons = wrapper.findAll('.fms-modal-footer button')
await buttons[buttons.length - 1].trigger('click')
await nextTick()
expect(lastSchema(wrapper)).toBe(null)
wrapper.unmount()
})
})