Files
workspace/code/g3soft-erp/web/tests/views/module/module-management/ModuleAutoCodePanel.spec.ts
T
2026-10-16 17:30:17 +08:00

203 lines
7.8 KiB
TypeScript
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.
/**
* 自动编码面板(s_autocode):惰性建行 + 实时预览 + 校验。
*
* 关键点:模块从未配置自动编码时草稿保持空数组(打开面板即建行会把「无变更」
* diff 成一条 insert),只有用户首次操作才落行;且 defineModel 写入后不能立刻
* 回读 prop(要下一轮才回传),所以建行后返回的是 reactive 代理行。
*
* 「启用」开关在表单第一行,测试点面板里真实的 G3Switch 驱动。
*/
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import ModuleAutoCodePanel from '@/views/module/module-management/ModuleAutoCodePanel.vue'
import type { ModuleAutoCodeRow } from '@/views/module/module-management/utils'
const FIELDS = [
{ b_field: 'b_id', b_name: '编码', b_type: 'input', b_canuse: 1 },
{ b_field: 'b_no', b_name: '业务单号', b_type: 'input', b_canuse: 1 },
{ b_field: 'b_bz', b_name: '备注', b_type: 'textarea', b_canuse: 1 },
{ b_field: 'b_amount', b_name: '金额', b_type: 'money', b_canuse: 1 },
{ b_field: 'b_off', b_name: '停用字段', b_type: 'input', b_canuse: 0 },
]
/** 造一行规则:只写关心的字段,其余取启用态的常用默认值 */
function row(overrides: Partial<ModuleAutoCodeRow> = {}): ModuleAutoCodeRow {
return {
b_module_id: 'm1',
b_field: 'b_no',
b_prefix: '',
b_dateformat: 'yyyyMM',
b_separator: '-',
b_seqwidth: 5,
b_resettype: 'month',
b_startvalue: 1,
b_currentperiod: null,
b_currentvalue: 0,
b_canuse: 1,
...overrides,
}
}
/**
* 模拟父级 v-model:autocode:emit 回来要显式写回 prop,
* 否则复现不了「立刻回读旧值」的问题。
*/
function mountPanel(autocode: ModuleAutoCodeRow[] = []) {
const wrapper = mount(ModuleAutoCodePanel, {
props: {
moduleId: 'm1',
fields: FIELDS,
autocode,
'onUpdate:autocode': (value: ModuleAutoCodeRow[]) => wrapper.setProps({ autocode: value }),
},
})
return wrapper
}
describe('自动编码面板', () => {
it('首次拨「启用」时惰性建行并写回 b_canuse(defineModel 不能写完立刻回读)', async () => {
const wrapper = mountPanel()
// 模块从未配置自动编码:草稿为空,此时拨开关会走 ensureRow 建行
await wrapper.find('[role="switch"]').trigger('click')
await wrapper.vm.$nextTick()
const emitted = wrapper.emitted('update:autocode')
expect(emitted).toBeTruthy()
const created = emitted![0][0] as ModuleAutoCodeRow[]
expect(created[0].b_canuse).toBe(1)
expect(created[0].b_module_id).toBe('m1')
// 业务编号字段自动取第一个可用文本字段(排除主键 b_id、非文本类型与停用字段)
expect(created[0].b_field).toBe('b_no')
})
it('已有一行时就地改,不再新建也不重新 emit 整个数组', async () => {
// 传进去的数组即父级持有的草稿:断言它被就地改写
const draft = [row({ b_canuse: 0, b_prefix: 'A' })]
const wrapper = mountPanel(draft)
await wrapper.find('[role="switch"]').trigger('click')
await wrapper.vm.$nextTick()
// 已落草稿:直接改这一行的属性(父级 ref 是深度响应式,改得见)
expect(wrapper.emitted('update:autocode')).toBeUndefined()
expect(draft[0]?.b_canuse).toBe(1)
expect(draft[0]?.b_prefix).toBe('A') // 沿用原行,未被占位行覆盖
})
it('把从未配置过的模块拨开又关掉,不留占位行(否则保存会 diff 出一条 insert)', async () => {
const wrapper = mountPanel()
await wrapper.find('[role="switch"]').trigger('click')
await wrapper.vm.$nextTick()
await wrapper.find('[role="switch"]').trigger('click')
await wrapper.vm.$nextTick()
// 最后一次写回的草稿必须回到空数组
const emitted = wrapper.emitted('update:autocode')!
expect(emitted[emitted.length - 1][0]).toEqual([])
})
it('停用既有规则时保留该行(那是既有规则的停用,要落库)', async () => {
const draft = [row({ b_canuse: 1 })]
const wrapper = mountPanel(draft)
await wrapper.find('[role="switch"]').trigger('click')
await wrapper.vm.$nextTick()
expect(draft[0]?.b_canuse).toBe(0)
// 没有把数组清空重写
expect(wrapper.emitted('update:autocode')).toBeUndefined()
})
it('开关的选中态跟随规则行(配过又停用的行显示为关)', async () => {
const on = mountPanel([row({ b_canuse: 1 })])
await on.vm.$nextTick()
expect(on.find('[role="switch"]').attributes('aria-checked')).toBe('true')
const off = mountPanel([row({ b_canuse: 0 })])
await off.vm.$nextTick()
expect(off.find('[role="switch"]').attributes('aria-checked')).toBe('false')
})
it('紧凑网格固定竖排且标签左对齐,不跟随全局横排配置', () => {
const wrapper = mountPanel()
expect(wrapper.find('form').classes()).toContain('g3-form--vertical')
// 标签左对齐依赖 G3Form 的默认 labelAlign=left;竖排下若回到 right 会被推到整列右边缘
expect(wrapper.findAll('.g3-form-item__label.is-label-right')).toHaveLength(0)
})
it('「启用」开关独占表单第一行,未配置时也可拨', () => {
const wrapper = mountPanel()
// 标签顺序 = 填写顺序:行内从左到右、行间从上到下
// (启用 → 业务编号字段/重置周期 → 前缀/日期/分隔符/位数/起始值 → 编号预览)
const labels = wrapper.findAll('.g3-form-item__label')
expect(labels.map((item) => item.text())).toEqual([
'启用',
'业务编号字段',
'重置周期',
'前缀',
'日期',
'分隔符',
'位数',
'起始值',
'编号预览',
])
// 未配置(草稿为空)时开关也渲染、可拨:它就是配置入口
const switchEl = wrapper.find('[role="switch"]')
expect(switchEl.exists()).toBe(true)
expect(switchEl.attributes('disabled')).toBeUndefined()
expect(switchEl.attributes('aria-checked')).toBe('false')
})
it('预览按 前缀 + 日期 + 补位流水号 拼接', async () => {
const wrapper = mountPanel([
row({ b_prefix: 'G3', b_dateformat: '', b_seqwidth: 4, b_resettype: 'none' }),
])
await wrapper.vm.$nextTick()
const preview = wrapper.find('[data-field="b_preview"] input')
expect(preview.attributes('readonly')).toBeDefined()
expect((preview.element as HTMLInputElement).value).toBe('G3-0001')
})
it('启用态下的合法规则校验通过', async () => {
const wrapper = mountPanel([row()])
expect(await wrapper.vm.validate()).toEqual([])
})
it('启用但未选业务编号字段时报错(错误挂在对应 FormItem 上)', async () => {
const wrapper = mountPanel([row({ b_field: null })])
const errors = await wrapper.vm.validate()
expect(errors.map((item) => item.name)).toContain('b_field')
expect(errors.find((item) => item.name === 'b_field')?.errors[0]).toBe('请选择业务编号字段')
})
it('位数超出 1-18 时报错', async () => {
const wrapper = mountPanel([row({ b_seqwidth: 20 })])
const errors = await wrapper.vm.validate()
expect(errors.find((item) => item.name === 'b_seqwidth')?.errors[0]).toBe(
'流水号位数必须是 1 到 18 之间的整数',
)
})
it('未启用时不校验(占位行只用于展示)', async () => {
const wrapper = mountPanel()
expect(await wrapper.vm.validate()).toEqual([])
})
it('未启用时即使参数残缺也不报错(重置周期为空同样跳过)', async () => {
// 占位行 / 已停用的规则都不该拦保存:旧写法里 b_resettype 是硬性 required,会误报
const wrapper = mountPanel([row({ b_canuse: 0, b_resettype: '', b_seqwidth: 0 })])
expect(await wrapper.vm.validate()).toEqual([])
})
})