import { mount, shallowMount } from '@vue/test-utils';
|
import { nextTick, reactive, watch } from 'vue';
|
|
import { Input as AInput } from 'ant-design-vue';
|
import { describe, expect, it, vi } from 'vitest';
|
|
import ParameterTable from '../ParameterTable.vue';
|
import tableSource from '../ParameterTable.vue?raw';
|
|
const tableState = vi.hoisted(() => ({ config: undefined as any }));
|
|
vi.mock('@jnpf/ui/vxeTable', () => ({
|
BasicVxeTable: {
|
name: 'BasicVxeTable',
|
props: ['dataSource'],
|
template:
|
'<div><slot v-if="dataSource?.length" name="canshuMingcheng" :record="dataSource[0]" /><slot v-if="dataSource?.length" name="shujuLeixing" :record="dataSource[0]" /><slot v-if="dataSource?.length" name="danwei" :record="dataSource[0]" /></div>',
|
},
|
useVxeTable: (config: any) => {
|
tableState.config = config;
|
return [vi.fn()];
|
},
|
}));
|
|
describe('eLN formula parameter table', () => {
|
it('only allows draft slots to be edited or removed', () => {
|
const wrapper = mount(ParameterTable, {
|
props: { modelValue: [], readonly: true, status: 'PRE_RELEASE' },
|
global: {
|
stubs: {
|
'a-alert': { props: ['message'], template: '<span>{{ message }}</span>' },
|
'a-button': true,
|
'a-input': true,
|
'a-input-number': true,
|
'a-select': true,
|
'a-switch': true,
|
'a-tag': true,
|
},
|
},
|
});
|
expect(wrapper.text()).toContain('集合参数仅支持映射重复区域的数值字段,不支持普通字段或逐行公式');
|
expect(tableSource).toContain("status === 'DRAFT'");
|
expect(tableSource).toContain('canshuBianma');
|
expect(tableSource).toContain('DECIMAL_LIST');
|
});
|
|
it('adds only business properties and leaves the generated code to the backend', () => {
|
expect(tableSource).toContain("canshuMingcheng: ''");
|
expect(tableSource).not.toContain("canshuBianma: ''");
|
});
|
|
it('uses a stable client key for unsaved parameter rows', () => {
|
expect(tableSource).toContain('row._clientKey || row.id');
|
expect(tableSource).toContain('_clientKey:');
|
});
|
|
it('uses a stable height when embedded in a formula modal', () => {
|
shallowMount(ParameterTable, {
|
props: { modelValue: [], status: 'DRAFT' },
|
global: { stubs: { 'a-alert': true, 'a-button': true, 'a-input': true, 'a-input-number': true, 'a-select': true, 'a-switch': true } },
|
});
|
|
expect(tableState.config.height).toBe(220);
|
});
|
|
it('uses its own stable height when it replaces the formula workspace', () => {
|
const wrapper = shallowMount(ParameterTable, {
|
props: { fullHeight: true, modelValue: [], status: 'DRAFT' },
|
global: { stubs: { 'a-alert': true, 'a-button': true, 'a-input': true, 'a-input-number': true, 'a-select': true, 'a-switch': true } },
|
});
|
|
expect(typeof tableState.config.height).toBe('number');
|
expect(tableState.config.height).toBeGreaterThanOrEqual(360);
|
expect(wrapper.classes()).toContain('is-full-height');
|
});
|
|
it('maps parameter type option labels and values for the native select', () => {
|
const wrapper = mount(ParameterTable, {
|
props: {
|
modelValue: [{ canshuBianma: 'amount', canshuMingcheng: '含量', paixu: 1, shifouBitian: 1, shujuLeixing: 'DECIMAL' }],
|
status: 'DRAFT',
|
},
|
global: {
|
stubs: {
|
'a-alert': true,
|
'a-button': true,
|
'a-input': true,
|
'a-input-number': true,
|
'a-select': { name: 'ASelect', props: ['fieldNames', 'options', 'value'], template: '<div />' },
|
'a-switch': true,
|
},
|
},
|
});
|
|
const typeSelect = wrapper.findComponent({ name: 'ASelect' });
|
expect(typeSelect.props('fieldNames')).toEqual({ label: 'fullName', value: 'id' });
|
expect(typeSelect.props('options')).toContainEqual({ fullName: '数值', id: 'DECIMAL' });
|
});
|
|
it('provides prompts for editable parameter fields and enough room for sorting', () => {
|
shallowMount(ParameterTable, {
|
props: { modelValue: [], status: 'DRAFT' },
|
global: { stubs: { 'a-alert': true, 'a-button': true, 'a-input': true, 'a-input-number': true, 'a-select': true, 'a-switch': true } },
|
});
|
|
expect(tableSource).toContain('placeholder="请输入参数名称"');
|
expect(tableSource).toContain('placeholder="请选择类型"');
|
expect(tableSource).toContain('placeholder="请输入单位"');
|
expect(tableSource).toContain('placeholder="请输入排序"');
|
expect(tableState.config.columns.find((column: any) => column.dataIndex === 'paixu').width).toBe(110);
|
});
|
|
it('uses a compact action header without adding space below the notice', () => {
|
expect(tableSource).not.toContain('<span>参数槽</span>');
|
expect(tableSource).toContain('<a-button v-if="editable" type="default"');
|
expect(tableSource).toContain('<a-alert banner class="parameter-table__notice"');
|
expect(tableSource).toMatch(/\.parameter-table__notice\s*\{[^}]*margin-top: 8px/);
|
expect(tableSource).not.toMatch(/\.parameter-table__notice\s*\{[^}]*margin-bottom:/);
|
});
|
|
it('updates a unit without triggering a reactive table refresh', async () => {
|
const parameter = reactive({
|
_clientKey: 'row-1',
|
canshuMingcheng: '含量',
|
danwei: undefined as string | undefined,
|
paixu: 1,
|
shifouBitian: 1 as const,
|
shujuLeixing: 'DECIMAL' as const,
|
});
|
let reactiveUpdates = 0;
|
watch(
|
() => parameter.danwei,
|
() => reactiveUpdates++,
|
);
|
const wrapper = mount(ParameterTable, {
|
props: { modelValue: [parameter], status: 'DRAFT' },
|
global: {
|
stubs: {
|
'a-alert': true,
|
'a-button': true,
|
'a-input': { name: 'AInput', props: ['value'], template: '<input />' },
|
'a-input-number': true,
|
'a-select': true,
|
'a-switch': true,
|
},
|
},
|
});
|
|
const unitInput = wrapper.findAllComponents({ name: 'AInput' }).find((input) => input.props('value') === undefined)!;
|
unitInput.vm.$emit('update:value', 'mg/mL');
|
await nextTick();
|
|
expect(unitInput.props('value')).toBe('mg/mL');
|
expect(parameter.danwei).toBe('mg/mL');
|
expect(reactiveUpdates).toBe(0);
|
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
|
});
|
|
it('updates a parameter name without triggering a reactive table refresh', async () => {
|
const parameter = reactive({
|
_clientKey: 'row-1',
|
canshuMingcheng: '含量',
|
danwei: undefined as string | undefined,
|
paixu: 1,
|
shifouBitian: 1 as const,
|
shujuLeixing: 'DECIMAL' as const,
|
});
|
let reactiveUpdates = 0;
|
watch(
|
() => parameter.canshuMingcheng,
|
() => reactiveUpdates++,
|
);
|
const wrapper = mount(ParameterTable, {
|
props: { modelValue: [parameter], status: 'DRAFT' },
|
global: {
|
stubs: {
|
'a-alert': true,
|
'a-button': true,
|
'a-input': { name: 'AInput', props: ['value'], template: '<input />' },
|
'a-input-number': true,
|
'a-select': true,
|
'a-switch': true,
|
},
|
},
|
});
|
|
const nameInput = wrapper.findAllComponents({ name: 'AInput' }).find((input) => input.props('value') === '含量')!;
|
nameInput.vm.$emit('update:value', '水分');
|
await nextTick();
|
|
expect(nameInput.props('value')).toBe('水分');
|
expect(parameter.canshuMingcheng).toBe('水分');
|
expect(reactiveUpdates).toBe(0);
|
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
|
});
|
|
it('keeps real Ant inputs interactive while synchronizing submitted text values', async () => {
|
const parameter = reactive({
|
_clientKey: 'row-1',
|
canshuMingcheng: '',
|
danwei: undefined as string | undefined,
|
paixu: 1,
|
shifouBitian: 1 as const,
|
shujuLeixing: 'DECIMAL' as const,
|
});
|
const wrapper = mount(ParameterTable, {
|
props: { modelValue: [parameter], status: 'DRAFT' },
|
global: {
|
components: { AInput },
|
stubs: {
|
'a-alert': true,
|
'a-button': true,
|
'a-input-number': true,
|
'a-select': true,
|
'a-switch': true,
|
},
|
},
|
});
|
|
const nameInput = wrapper.find('input[placeholder="请输入参数名称"]');
|
const unitInput = wrapper.find('input[placeholder="请输入单位"]');
|
await nameInput.setValue('水分');
|
await unitInput.setValue('mg/mL');
|
|
expect((nameInput.element as HTMLInputElement).value).toBe('水分');
|
expect((unitInput.element as HTMLInputElement).value).toBe('mg/mL');
|
expect(parameter.canshuMingcheng).toBe('水分');
|
expect(parameter.danwei).toBe('mg/mL');
|
});
|
});
|