276 lines
9.7 KiB
JavaScript
276 lines
9.7 KiB
JavaScript
/**
|
|
* 计算公式 · 运行期集成测试(FmsModuleEditModal)。
|
|
* 覆盖:打开旧单据不重算、依赖变更触发重算、链式公式收敛、覆写与恢复、
|
|
* 保存请求携带 moduleCode / formulaRevision / formulaOverrides。
|
|
*/
|
|
import { mount } from '@vue/test-utils'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import { nextTick } from 'vue'
|
|
|
|
const { saveObjectApi } = vi.hoisted(() => ({ saveObjectApi: vi.fn(async () => ({})) }))
|
|
|
|
vi.mock('@/services/api', () => ({
|
|
loadDataApi: vi.fn(async (viewName, condition) => {
|
|
// 第二个模块(b_order2)用于验证「被公式引用的隐藏字段(静默字段)」:条件里带模块编码即可区分
|
|
const hidden = String(condition || '').includes('b_order2')
|
|
if (viewName === 's_module') {
|
|
return {
|
|
data: [
|
|
hidden
|
|
? {
|
|
b_id: 'b_order2',
|
|
b_name: '订单2',
|
|
b_module_type: 'data',
|
|
b_canuse: 1,
|
|
b_view_table: 'b_order2',
|
|
b_save_table: 'b_order2',
|
|
b_key_field: 'b_id',
|
|
}
|
|
: {
|
|
b_id: 'b_order',
|
|
b_name: '订单',
|
|
b_module_type: 'data',
|
|
b_canuse: 1,
|
|
b_view_table: 'b_order',
|
|
b_save_table: 'b_order',
|
|
b_key_field: 'b_id',
|
|
},
|
|
],
|
|
}
|
|
}
|
|
if (viewName === 's_field') {
|
|
return {
|
|
data: hidden
|
|
? [
|
|
{ b_field: 'b_id', b_name: '单号', b_type: 'input', b_canuse: 1 },
|
|
{ b_field: 'b_qty', b_name: '数量', b_type: 'number', b_canuse: 1 },
|
|
{ b_field: 'b_price', b_name: '单价', b_type: 'money', b_canuse: 1 },
|
|
{ b_field: 'b_amount', b_name: '金额', b_type: 'money', b_canuse: 1 },
|
|
{ b_field: 'b_rate', b_name: '系数', b_type: 'number', b_canuse: 1 },
|
|
]
|
|
: [
|
|
{ b_field: 'b_id', b_name: '单号', b_type: 'input', b_canuse: 1 },
|
|
{ b_field: 'b_qty', b_name: '数量', b_type: 'number', b_canuse: 1 },
|
|
{ b_field: 'b_price', b_name: '单价', b_type: 'money', b_canuse: 1 },
|
|
{ b_field: 'b_amount', b_name: '金额', b_type: 'money', b_canuse: 1 },
|
|
{ b_field: 'b_tax', b_name: '税额', b_type: 'money', b_canuse: 1 },
|
|
],
|
|
}
|
|
}
|
|
if (viewName === 's_module_schema') {
|
|
return {
|
|
data: [
|
|
{
|
|
b_module_id: hidden ? 'b_order2' : 'b_order',
|
|
b_schema_type: 'edit',
|
|
b_schema_json: JSON.stringify(
|
|
hidden
|
|
? {
|
|
schemaVersion: 2,
|
|
children: [
|
|
{
|
|
type: 'row',
|
|
children: [
|
|
{ field: 'b_qty', span: 12 },
|
|
{ field: 'b_price', span: 12 },
|
|
],
|
|
},
|
|
{
|
|
type: 'row',
|
|
children: [
|
|
{
|
|
field: 'b_amount',
|
|
readonly: true,
|
|
formula: 'ROUND(b_qty * b_price * b_rate, 2)',
|
|
},
|
|
],
|
|
},
|
|
// 隐藏的依赖字段:只取值、不渲染(静默字段)
|
|
{ type: 'row', children: [{ field: 'b_rate', visible: false }] },
|
|
],
|
|
}
|
|
: {
|
|
schemaVersion: 2,
|
|
children: [
|
|
{
|
|
type: 'group',
|
|
id: 'g1',
|
|
title: '基础信息',
|
|
children: [
|
|
{
|
|
type: 'row',
|
|
children: [
|
|
{ field: 'b_qty', span: 12 },
|
|
{ field: 'b_price', span: 12 },
|
|
],
|
|
},
|
|
{
|
|
type: 'row',
|
|
children: [
|
|
{
|
|
field: 'b_amount',
|
|
readonly: true,
|
|
formula: 'ROUND(b_qty * b_price, 2)',
|
|
},
|
|
{
|
|
field: 'b_tax',
|
|
readonly: true,
|
|
formula: 'b_amount * 0.1',
|
|
overridable: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
),
|
|
},
|
|
],
|
|
}
|
|
}
|
|
return { data: [] }
|
|
}),
|
|
saveObjectApi,
|
|
nextIdApi: vi.fn(async () => ({ data: '1' })),
|
|
loadDataBySqlApi: vi.fn(async () => ({ data: [] })),
|
|
}))
|
|
|
|
import FmsModuleEditModal from '@/components/fms-module-edit/FmsModuleEditModal.vue'
|
|
|
|
/** 弹窗内的输入框(按表单顺序):[b_qty, b_price, b_amount, b_tax] */
|
|
function inputs() {
|
|
return [...document.querySelectorAll('.fms-modal-body input')]
|
|
}
|
|
|
|
/** 按索引写值并触发 input(v-model 口径),然后等一轮渲染 */
|
|
async function setInput(index, value) {
|
|
const input = inputs()[index]
|
|
input.value = value
|
|
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
await nextTick()
|
|
}
|
|
|
|
function buttonByText(text) {
|
|
return [...document.querySelectorAll('.fms-modal-footer button, .fms-modal-body button')].find(
|
|
(button) => button.textContent.trim() === text,
|
|
)
|
|
}
|
|
|
|
async function openModal(record, dataCode = 'b_order') {
|
|
const wrapper = mount(FmsModuleEditModal, {
|
|
props: { open: false, dataCode, record },
|
|
attachTo: document.body,
|
|
})
|
|
await wrapper.setProps({ open: true })
|
|
await new Promise((resolve) => setTimeout(resolve, 40))
|
|
await nextTick()
|
|
return wrapper
|
|
}
|
|
|
|
const STORED = { b_id: 'O1', b_qty: 9, b_price: '10.00', b_amount: '99', b_tax: '9.9' }
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
document.body.innerHTML = ''
|
|
saveObjectApi.mockClear()
|
|
})
|
|
|
|
describe('计算公式 · 运行期', () => {
|
|
it('打开旧单据不重算,原有计算值原样保留', async () => {
|
|
const wrapper = await openModal({ ...STORED })
|
|
expect(document.querySelector('.fme-formula-banner')).toBeNull()
|
|
expect(inputs()[2].value).toBe('99')
|
|
expect(inputs()[3].value).toBe('9.9')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('依赖字段变更触发重算,链式公式逐级收敛', async () => {
|
|
const wrapper = await openModal({ ...STORED })
|
|
await setInput(0, '2')
|
|
expect(inputs()[2].value).toBe('20')
|
|
expect(inputs()[3].value).toBe('2')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('覆写后不再被公式覆盖,恢复公式回到计算结果', async () => {
|
|
const wrapper = await openModal({ ...STORED })
|
|
buttonByText('手工调整').click()
|
|
await nextTick()
|
|
await setInput(3, '5')
|
|
|
|
await setInput(0, '3')
|
|
expect(inputs()[2].value).toBe('30')
|
|
expect(inputs()[3].value).toBe('5') // 覆写值不被覆盖
|
|
|
|
buttonByText('恢复公式').click()
|
|
await nextTick()
|
|
expect(inputs()[3].value).toBe('3')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('保存请求带 moduleCode / formulaRevision / formulaOverrides,且计算值进入更新', async () => {
|
|
const wrapper = await openModal({ ...STORED })
|
|
await setInput(0, '2')
|
|
buttonByText('确定').click()
|
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
|
|
const [requests] = saveObjectApi.mock.calls.at(-1)
|
|
const request = requests[0]
|
|
expect(request.table).toBe('b_order')
|
|
expect(request.moduleCode).toBe('b_order')
|
|
expect(String(request.formulaRevision).startsWith('fnv1a64:')).toBe(true)
|
|
expect(request.formulaOverrides).toBeUndefined()
|
|
const updated = request.updates[0]
|
|
expect(String(updated.b_qty)).toBe('2')
|
|
expect(updated.b_amount).toBe('20')
|
|
expect(updated.b_tax).toBe('2')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('覆写声明随请求提交(不落业务字段)', async () => {
|
|
const wrapper = await openModal({ ...STORED })
|
|
buttonByText('手工调整').click()
|
|
await nextTick()
|
|
await setInput(3, '5')
|
|
buttonByText('确定').click()
|
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
|
|
const [requests] = saveObjectApi.mock.calls.at(-1)
|
|
expect(requests[0].formulaOverrides).toEqual(['b_tax'])
|
|
expect(requests[0].updates[0].b_tax).toBe('5')
|
|
wrapper.unmount()
|
|
})
|
|
})
|
|
|
|
describe('计算公式 · 静默字段(被引用的隐藏字段)', () => {
|
|
it('隐藏依赖只取值不渲染,并参与重算', async () => {
|
|
const wrapper = await openModal(
|
|
{ b_id: 'O2', b_qty: 2, b_price: '10.00', b_amount: '10', b_rate: 0.5 },
|
|
'b_order2',
|
|
)
|
|
// 隐藏字段不渲染:只有 数量 / 单价 / 金额 三个输入
|
|
expect(inputs().length).toBe(3)
|
|
await setInput(0, '4')
|
|
expect(inputs()[2].value).toBe('20')
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('隐藏依赖为空时按空值参与运算,不阻断保存', async () => {
|
|
const wrapper = await openModal(
|
|
{ b_id: 'O2', b_qty: 2, b_price: '10.00', b_amount: '10' },
|
|
'b_order2',
|
|
)
|
|
await setInput(0, '4')
|
|
expect(inputs()[2].value).toBe('0')
|
|
|
|
buttonByText('确定').click()
|
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
const [requests] = saveObjectApi.mock.calls.at(-1)
|
|
expect(requests[0].moduleCode).toBe('b_order2')
|
|
expect(requests[0].updates[0].b_amount).toBe('0')
|
|
wrapper.unmount()
|
|
})
|
|
})
|