/** * 查询配置面板回写语义(module-management): * - query 行草稿与 query JSON 不是一一映射(缺省 component/quick/groups、运行时写入的 * area 等),只加载(含保存后同模块重载、schema ref 换新对象)不得回写, * 否则草稿与库中基线文本不一致 → 切树节点误报「未保存」,并静默改写未建模属性; * - 用户真实编辑(改操作符/组件/默认值、增删行)必须回写; * - 回写保留编辑器未建模的条件属性(如 area)。 */ 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' // jsdom 无布局:StkTable 在 clientWidth 为 0 时启用虚拟列裁剪,补成宽容器 beforeAll(() => { Object.defineProperty(HTMLElement.prototype, 'clientWidth', { configurable: true, get() { return 5000 }, }) }) /** 库中历史查询配置:缺 quick 键、条件未显式写 component,且带运行时写入的 area */ const legacySchema = { schemaVersion: 1, groups: [ { logic: 'and', conditions: [ { field: 'b_name', operator: 'like', area: 'hidden' }, { field: 'b_amount', operator: 'eq', component: 'number' }, ], }, ], } const fields = [ { b_field: 'b_name', b_name: '名称', b_type: 'input', b_canuse: 1 }, { b_field: 'b_amount', b_name: '金额', b_type: 'money', b_canuse: 1 }, ] 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 } beforeEach(() => { document.body.innerHTML = '' }) describe('查询配置面板:加载不回写、编辑才回写', () => { it('仅加载(schema 换新对象)不回写草稿', async () => { const wrapper = await mountPanel(legacySchema) expect(lastSchema(wrapper)).toBe(null) // 模拟父级重载:同一份历史 JSON 以新引用下发(与 parseSchema 后的重载一致) await wrapper.setProps({ modelValue: JSON.parse(JSON.stringify(legacySchema)) }) await nextTick() expect(lastSchema(wrapper)).toBe(null) // 再模拟一次:父级下发的已是规范化 JSON,同样不得回写 await wrapper.setProps({ modelValue: { schemaVersion: 1, quick: [], groups: [ { logic: 'and', conditions: [ { field: 'b_name', operator: 'like', component: 'input', area: 'hidden' }, { field: 'b_amount', operator: 'eq', component: 'number' }, ], }, ], }, }) await nextTick() expect(lastSchema(wrapper)).toBe(null) }) it('用户编辑操作符时回写,并保留未建模的 area 属性与 quick', async () => { const wrapper = await mountPanel({ ...legacySchema, quick: ['b_name'] }) wrapper.vm.queryConfig = wrapper.vm.queryConfig.map((row, index) => index === 0 ? { ...row, b_operator: 'eq' } : row, ) await nextTick() const next = lastSchema(wrapper) expect(next).not.toBe(null) expect(next.quick).toEqual(['b_name']) expect(next.groups[0].conditions[0]).toEqual({ field: 'b_name', operator: 'eq', component: 'input', area: 'hidden', }) }) it('改字段后不复用原字段的扩展属性', async () => { const wrapper = await mountPanel(legacySchema) wrapper.vm.queryConfig = wrapper.vm.queryConfig.map((row, index) => index === 0 ? { ...row, b_field: 'b_amount', b_component: 'number', b_operator: 'eq' } : row, ) await nextTick() const next = lastSchema(wrapper) expect(next.groups[0].conditions[0]).toEqual({ field: 'b_amount', operator: 'eq', component: 'number', }) }) it('删除行后回写(条件为空 → groups 置空)', async () => { const wrapper = await mountPanel(legacySchema) wrapper.vm.queryConfig = [] await nextTick() const next = lastSchema(wrapper) expect(next.groups).toEqual([]) expect(next.quick).toEqual([]) }) })