import type { CrossTemplateScalarCandidate, CrossTemplateSourceTemplate, TemplateBinding, TemplateFieldOption } from '../../src/domain/types';
|
|
import { mount } from '@vue/test-utils';
|
|
import Antd, { Select } from 'ant-design-vue';
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
import FieldEditor from '../../src/components/FieldEditor.vue';
|
|
beforeAll(() => {
|
window.matchMedia ??= () =>
|
({
|
addEventListener() {},
|
addListener() {},
|
dispatchEvent: () => false,
|
matches: false,
|
media: '',
|
onchange: null,
|
removeEventListener() {},
|
removeListener() {},
|
}) as MediaQueryList;
|
globalThis.ResizeObserver ??= class ResizeObserver {
|
disconnect() {}
|
observe() {}
|
unobserve() {}
|
};
|
});
|
|
const fields: TemplateFieldOption[] = [
|
{
|
alias: '样品编号',
|
controlConfig: { defaultValue: 'S-001', placeholder: '请输入样品编号' },
|
fieldCode: 'sample_code',
|
fieldId: 'field-global',
|
fieldVersionId: 'version-global',
|
isCurrent: true,
|
label: '样品编号',
|
required: true,
|
scope: 'GLOBAL',
|
source: 'SYSTEM',
|
type: 'TEXT',
|
versionStatus: 'PUBLISHED',
|
},
|
{
|
alias: '检测结论',
|
controlConfig: {
|
options: [
|
{ display: '合格', value: 'PASS' },
|
{ display: '不合格', value: 'FAIL' },
|
],
|
placeholder: '请选择',
|
},
|
fieldCode: 'result',
|
fieldId: 'field-private',
|
fieldVersionId: 'version-private',
|
isCurrent: true,
|
label: '检测结论',
|
required: false,
|
scope: 'TEMPLATE',
|
source: 'NONE',
|
type: 'SELECT',
|
versionStatus: 'PRE_RELEASE',
|
},
|
];
|
const crossTemplateCandidates: CrossTemplateScalarCandidate[] = [
|
{
|
sourceFieldCode: 'source_result',
|
sourceFieldId: 'source-field',
|
sourceFieldName: '来源检验结果',
|
sourceFieldType: 'DECIMAL',
|
sourceFieldVersionId: 'source-version-2',
|
sourceTemplateCode: 'SOURCE-TPL-001',
|
sourceTemplateId: 'source-template',
|
},
|
];
|
const crossTemplateSourceTemplates: CrossTemplateSourceTemplate[] = [
|
{ sourceTemplateCode: 'SOURCE-TPL-001', sourceTemplateId: 'source-template' },
|
{ sourceTemplateCode: 'ENV-TPL-002', sourceTemplateId: 'environment-template' },
|
];
|
const editableCheckbox: TemplateFieldOption = {
|
alias: '合格',
|
controlConfig: { defaultChecked: false },
|
fieldCode: 'passed',
|
fieldId: 'passed-field',
|
fieldVersionId: 'passed-version',
|
isCurrent: true,
|
label: '合格',
|
required: false,
|
scope: 'TEMPLATE',
|
source: 'NONE',
|
type: 'CHECKBOX',
|
versionStatus: 'PUBLISHED',
|
};
|
const readOnlyCheckbox: TemplateFieldOption = {
|
...editableCheckbox,
|
alias: '系统判定',
|
fieldCode: 'system_result',
|
fieldId: 'system-result-field',
|
fieldVersionId: 'system-result-version',
|
label: '系统判定',
|
source: 'SYSTEM',
|
};
|
function checkboxBinding(id: string, groupCode: string, stepId: string): TemplateBinding {
|
return {
|
checkboxGroupCode: groupCode,
|
controlAlias: id,
|
controlConfig: { defaultChecked: false },
|
controlRole: 'PRIMARY',
|
controlTag: `eln.field.${id}`,
|
controlType: 'CHECKBOX',
|
fieldCode: id,
|
fieldId: `${id}-field`,
|
fieldVersionId: `${id}-version`,
|
id,
|
primaryBindingId: null,
|
status: 'ACTIVE',
|
stepId,
|
templateId: 'template',
|
version: 1,
|
};
|
}
|
|
function mountEditor(disabled = false) {
|
return mount(FieldEditor, { props: { disabled, fields }, global: { plugins: [Antd] } });
|
}
|
|
describe('fieldEditor', () => {
|
it('将标注当前位置放在两个字段入口上方', () => {
|
const wrapper = mountEditor();
|
|
expect(
|
wrapper
|
.findAll('[data-test="submit-field"], [data-test="open-private-field"], [data-test="open-cross-template"]')
|
.map((item) => item.attributes('data-test')),
|
).toEqual(['submit-field', 'open-private-field', 'open-cross-template']);
|
expect(wrapper.get('[data-test="submit-field"]').classes()).toContain('ant-btn-primary');
|
expect(wrapper.get('[data-test="open-private-field"]').classes()).toContain('ant-btn-dashed');
|
expect(wrapper.get('[data-test="open-cross-template"]').classes()).toContain('ant-btn-dashed');
|
});
|
|
it('按公共字段与本模板字段分组搜索并标记预发布风险', async () => {
|
const wrapper = mountEditor();
|
const select = wrapper.findComponent(Select);
|
|
expect(select.props('allowClear')).toBe(true);
|
expect(select.props('options')).toEqual([
|
expect.objectContaining({ label: '公共字段', options: [expect.objectContaining({ value: 'version-global' })] }),
|
expect.objectContaining({ label: '本模板字段', options: [expect.objectContaining({ value: 'version-private' })] }),
|
]);
|
select.vm.$emit('update:value', 'version-private');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.get('[data-test="pre-release-risk"]').text()).toContain('预发布');
|
expect(wrapper.text()).toContain('检测结论');
|
|
select.vm.$emit('update:value', undefined);
|
await wrapper.vm.$nextTick();
|
expect(wrapper.find('[data-test="field-properties"]').exists()).toBe(false);
|
expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
|
});
|
|
it('不提供自由文本字段编码,并平铺展示全部只读属性', async () => {
|
const wrapper = mountEditor();
|
expect(wrapper.find('[data-test="field-code"]').exists()).toBe(false);
|
expect(wrapper.find('[data-test="mode-editor"]').exists()).toBe(false);
|
expect(wrapper.find('[data-test="annotation-mode"]').exists()).toBe(true);
|
|
wrapper.findComponent(Select).vm.$emit('update:value', 'version-global');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.get('[data-test="field-alias-readonly"]').text()).toContain('样品编号');
|
expect(wrapper.get('[data-test="field-scope-readonly"]').text()).toContain('公共字段');
|
expect(wrapper.get('[data-test="field-type-readonly"]').text()).toContain('单行文本');
|
expect(wrapper.get('[data-test="field-required-readonly"]').text()).toContain('必填');
|
expect(wrapper.find('[data-test="field-config"]').exists()).toBe(false);
|
expect(wrapper.get('[data-test="field-properties"]').classes()).toContain('field-properties');
|
expect(wrapper.findAll('.field-summary')).toHaveLength(1);
|
expect(wrapper.get('[data-test="field-placeholder-readonly"]').text()).toContain('请输入样品编号');
|
expect(wrapper.get('[data-test="field-default-readonly"]').text()).toContain('S-001');
|
});
|
|
it('只提交选中的后端字段版本,不允许覆盖 required/type/binding', async () => {
|
const wrapper = mountEditor();
|
wrapper.findComponent(Select).vm.$emit('update:value', 'version-global');
|
await wrapper.vm.$nextTick();
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
|
expect(wrapper.emitted('submit')).toEqual([[fields[0]]]);
|
expect(wrapper.html()).not.toContain('binding');
|
});
|
|
it('重复行模式提交组编码并固定使用所选字段版本', async () => {
|
const wrapper = mountEditor();
|
wrapper.findComponent(Select).vm.$emit('update:value', 'version-private');
|
wrapper.get('[data-test="annotation-mode"]').findComponent({ name: 'ASegmented' }).vm.$emit('update:value', 'repeat');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.get('[data-test="annotation-mode"]').text()).toContain('重复行');
|
expect(wrapper.get('[data-test="annotation-mode"]').text()).not.toContain('重复列');
|
wrapper.getComponent({ name: 'AAutoComplete' }).vm.$emit('update:value', 'test_results');
|
await wrapper.vm.$nextTick();
|
expect((wrapper.get('[data-test="repeat-min-rows"]').element as HTMLInputElement).value).toBe('0');
|
expect((wrapper.get('[data-test="repeat-max-rows"]').element as HTMLInputElement).value).toBe('100');
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
|
expect(wrapper.emitted('submit-repeat')).toEqual([[fields[1], { groupCode: 'test_results', maxRows: 100, minRows: 0 }]]);
|
});
|
|
it('重复组行数约束无效时禁止提交', async () => {
|
const wrapper = mountEditor();
|
wrapper.findComponent(Select).vm.$emit('update:value', 'version-private');
|
wrapper.get('[data-test="annotation-mode"]').findComponent({ name: 'ASegmented' }).vm.$emit('update:value', 'repeat');
|
await wrapper.vm.$nextTick();
|
wrapper.getComponent({ name: 'AAutoComplete' }).vm.$emit('update:value', 'results');
|
await wrapper.vm.$nextTick();
|
await wrapper.get('[data-test="repeat-min-rows"]').setValue('5');
|
await wrapper.get('[data-test="repeat-max-rows"]').setValue('4');
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
expect(wrapper.emitted('submit-repeat')).toBeUndefined();
|
});
|
|
it('重复组编码提供输入提示,格式非法时显示中文校验并禁止提交', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: { fields, repeatGroups: [{ groupCode: 'results', minRows: 2, maxRows: 20 }] },
|
global: { plugins: [Antd] },
|
});
|
wrapper.findComponent(Select).vm.$emit('update:value', 'version-private');
|
wrapper.get('[data-test="annotation-mode"]').findComponent({ name: 'ASegmented' }).vm.$emit('update:value', 'repeat');
|
await wrapper.vm.$nextTick();
|
|
const groupInput = wrapper.getComponent({ name: 'AAutoComplete' });
|
expect(groupInput.props('placeholder')).toBe('输入新编码或选择已有重复组');
|
expect(groupInput.props('options')).toEqual([{ label: 'results(2 至 20 行)', value: 'results' }]);
|
expect(wrapper.find('[data-test="repeat-group-error"]').exists()).toBe(false);
|
|
groupInput.vm.$emit('update:value', '含中文');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.get('[data-test="repeat-group-error"]').text()).toContain('字母、数字、下划线或连字符');
|
expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
expect(wrapper.emitted('submit-repeat')).toBeUndefined();
|
});
|
|
it('生成重复组编码后先展示建议,确认后才替换当前编码', async () => {
|
const suggestCode = vi.fn(async () => 'jianyan_jieguo');
|
const wrapper = mount(FieldEditor, {
|
props: { fields, suggestCode },
|
global: { plugins: [Antd] },
|
});
|
wrapper.get('[data-test="annotation-mode"]').findComponent({ name: 'ASegmented' }).vm.$emit('update:value', 'repeat');
|
await wrapper.vm.$nextTick();
|
const repeatGroupInput = wrapper.findAllComponents({ name: 'AAutoComplete' }).find((item) => item.attributes('data-test') === 'repeat-group-code');
|
expect(repeatGroupInput).toBeDefined();
|
repeatGroupInput!.vm.$emit('update:value', 'original_group');
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.get('[data-test="repeat-code-assist"]').attributes('aria-label')).toBe('中文转拼音');
|
await wrapper.get('[data-test="repeat-code-assist"]').trigger('click');
|
expect(wrapper.get('[data-test="code-assist-modal"]').text()).toContain('生成重复组编码');
|
expect(wrapper.get('[data-test="code-assist-generate"]').attributes('disabled')).toBeDefined();
|
|
await wrapper.get('[data-test="code-assist-text"]').setValue('检验结果');
|
await wrapper.get('[data-test="code-assist-generate"]').trigger('click');
|
await vi.waitFor(() => expect(suggestCode).toHaveBeenCalledWith('检验结果'));
|
|
expect(wrapper.find('[data-test="code-assist-modal"]').exists()).toBe(true);
|
expect(wrapper.get('[data-test="code-assist-suggestion"]').attributes('value')).toBe('jianyan_jieguo');
|
expect(repeatGroupInput!.props('value')).toBe('original_group');
|
|
await wrapper.get('[data-test="code-assist-confirm"]').trigger('click');
|
|
expect(wrapper.find('[data-test="code-assist-modal"]').exists()).toBe(false);
|
expect(repeatGroupInput!.props('value')).toBe('jianyan_jieguo');
|
});
|
|
it('仅可编辑复选框显示选择组,并按当前步骤提供成员数候选', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: {
|
bindings: [
|
checkboxBinding('passed', 'inspection_result', 'step-1'),
|
checkboxBinding('failed', 'inspection_result', 'step-1'),
|
checkboxBinding('other', 'other_step_group', 'step-2'),
|
],
|
currentStepId: 'step-1',
|
fields: [...fields, editableCheckbox, readOnlyCheckbox],
|
},
|
global: { plugins: [Antd] },
|
});
|
const fieldSelect = wrapper.findAllComponents(Select)[0]!;
|
|
fieldSelect.vm.$emit('update:value', editableCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
|
const groupInput = wrapper.getComponent({ name: 'AAutoComplete' });
|
expect(wrapper.find('[data-test="checkbox-group-code"]').exists()).toBe(true);
|
expect(groupInput.props('options')).toEqual([{ label: 'inspection_result(2 个成员)', value: 'inspection_result' }]);
|
|
fieldSelect.vm.$emit('update:value', readOnlyCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
expect(wrapper.find('[data-test="checkbox-group-code"]').exists()).toBe(false);
|
});
|
|
it('标注成功只清空字段并保留选择组,切换作用域后清空组编码', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: { bindings: [], currentStepId: 'step-1', fields: [...fields, editableCheckbox], resetToken: 0 },
|
global: { plugins: [Antd] },
|
});
|
const fieldSelect = wrapper.findAllComponents(Select)[0]!;
|
fieldSelect.vm.$emit('update:value', editableCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
const groupInput = wrapper.getComponent({ name: 'AAutoComplete' });
|
groupInput.vm.$emit('update:value', 'inspection_result');
|
await wrapper.vm.$nextTick();
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
|
expect(wrapper.emitted('submit')).toEqual([
|
[editableCheckbox, { groupCode: 'inspection_result', maxSelected: 1, minSelected: 0 }],
|
]);
|
await wrapper.setProps({ resetToken: 1 });
|
expect(fieldSelect.props('value')).toBeUndefined();
|
expect(groupInput.props('value')).toBe('inspection_result');
|
|
await wrapper.setProps({ currentStepId: 'step-2' });
|
expect(groupInput.props('value')).toBe('');
|
});
|
|
it('公共复选框使用推荐选择组预填任意上下限', async () => {
|
const recommended = {
|
...editableCheckbox,
|
recommendedCheckboxGroupCode: 'review_result',
|
recommendedCheckboxMaxSelected: null,
|
recommendedCheckboxMinSelected: 2,
|
scope: 'GLOBAL' as const,
|
};
|
const wrapper = mount(FieldEditor, {
|
props: { fields: [...fields, recommended] },
|
global: { plugins: [Antd] },
|
});
|
|
wrapper.findAllComponents(Select)[0]!.vm.$emit('update:value', recommended.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.getComponent({ name: 'AAutoComplete' }).props('value')).toBe('review_result');
|
expect((wrapper.get('[data-test="checkbox-group-min"]').element as HTMLInputElement).value).toBe('2');
|
expect(wrapper.get('[data-test="checkbox-group-unlimited"]').attributes('aria-checked')).toBe('true');
|
expect(wrapper.find('[data-test="checkbox-group-max"]').exists()).toBe(false);
|
await wrapper.get('[data-test="submit-field"]').trigger('click');
|
expect(wrapper.emitted('submit')).toEqual([
|
[recommended, { groupCode: 'review_result', maxSelected: null, minSelected: 2 }],
|
]);
|
});
|
|
it('选择已有选择组时回填并锁定模板权威规则', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: {
|
bindings: [checkboxBinding('passed', 'inspection_result', 'step-1')],
|
checkboxGroups: [{ groupCode: 'inspection_result', maxSelected: 3, minSelected: 1, version: 2 }],
|
currentStepId: 'step-1',
|
fields: [...fields, editableCheckbox],
|
},
|
global: { plugins: [Antd] },
|
});
|
wrapper.findAllComponents(Select)[0]!.vm.$emit('update:value', editableCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
wrapper.getComponent({ name: 'AAutoComplete' }).vm.$emit('update:value', 'inspection_result');
|
await wrapper.vm.$nextTick();
|
|
expect((wrapper.get('[data-test="checkbox-group-min"]').element as HTMLInputElement).value).toBe('1');
|
expect((wrapper.get('[data-test="checkbox-group-max"]').element as HTMLInputElement).value).toBe('3');
|
expect(wrapper.get('[data-test="checkbox-group-min"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="checkbox-group-max"]').attributes('disabled')).toBeDefined();
|
});
|
|
it('选择组编码非法时显示中文校验并禁止提交', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: { bindings: [], currentStepId: 'step-1', fields: [...fields, editableCheckbox] },
|
global: { plugins: [Antd] },
|
});
|
wrapper.findAllComponents(Select)[0]!.vm.$emit('update:value', editableCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
wrapper.getComponent({ name: 'AAutoComplete' }).vm.$emit('update:value', '含中文');
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.get('[data-test="checkbox-group-error"]').text()).toContain('字母、数字、下划线或连字符');
|
expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
|
});
|
|
it('选择组编码生成失败时保留窗口、中文输入和原编码', async () => {
|
const suggestCode = vi.fn(async () => {
|
throw new Error('分词服务暂不可用');
|
});
|
const wrapper = mount(FieldEditor, {
|
props: { bindings: [], currentStepId: 'step-1', fields: [...fields, editableCheckbox], suggestCode },
|
global: { plugins: [Antd] },
|
});
|
wrapper.findAllComponents(Select)[0]!.vm.$emit('update:value', editableCheckbox.fieldVersionId);
|
await wrapper.vm.$nextTick();
|
const checkboxGroupInput = wrapper
|
.findAllComponents({ name: 'AAutoComplete' })
|
.find((item) => item.attributes('data-test') === 'checkbox-group-code');
|
expect(checkboxGroupInput).toBeDefined();
|
checkboxGroupInput!.vm.$emit('update:value', 'original_group');
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.get('[data-test="checkbox-code-assist"]').attributes('aria-label')).toBe('中文转拼音');
|
await wrapper.get('[data-test="checkbox-code-assist"]').trigger('click');
|
expect(wrapper.get('[data-test="code-assist-modal"]').text()).toContain('生成选择组编码');
|
await wrapper.get('[data-test="code-assist-text"]').setValue('检验结果');
|
await wrapper.get('[data-test="code-assist-generate"]').trigger('click');
|
await vi.waitFor(() => expect(wrapper.get('[data-test="code-assist-error"]').text()).toContain('分词服务暂不可用'));
|
|
expect(wrapper.get('[data-test="code-assist-text"]').attributes('value')).toBe('检验结果');
|
expect(checkboxGroupInput!.props('value')).toBe('original_group');
|
expect(wrapper.find('[data-test="code-assist-modal"]').exists()).toBe(true);
|
});
|
|
it('选择已有重复组时回填并锁定版本约束', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: { fields, repeatGroups: [{ groupCode: 'results', minRows: 2, maxRows: 20 }] },
|
global: { plugins: [Antd] },
|
});
|
wrapper.get('[data-test="annotation-mode"]').findComponent({ name: 'ASegmented' }).vm.$emit('update:value', 'repeat');
|
await wrapper.vm.$nextTick();
|
wrapper.getComponent({ name: 'AAutoComplete' }).vm.$emit('update:value', 'results');
|
await wrapper.vm.$nextTick();
|
|
expect((wrapper.get('[data-test="repeat-min-rows"]').element as HTMLInputElement).value).toBe('2');
|
expect((wrapper.get('[data-test="repeat-max-rows"]').element as HTMLInputElement).value).toBe('20');
|
expect(wrapper.get('[data-test="repeat-min-rows"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="repeat-max-rows"]').attributes('disabled')).toBeDefined();
|
});
|
|
it('进入面板内二级页面创建本模板字段并可返回', async () => {
|
const wrapper = mountEditor();
|
await wrapper.get('[data-test="open-private-field"]').trigger('click');
|
expect(wrapper.find('[data-test="private-field-form"]').exists()).toBe(true);
|
expect(wrapper.find('[data-test="annotation-main"]').exists()).toBe(false);
|
const backButton = wrapper.get('[data-test="private-field-back"]');
|
expect(backButton.classes()).toContain('secondary-page-back');
|
await wrapper.get('[data-test="private-field-back"]').trigger('click');
|
expect(wrapper.find('[data-test="private-field-form"]').exists()).toBe(false);
|
expect(wrapper.find('[data-test="annotation-main"]').exists()).toBe(true);
|
|
await wrapper.get('[data-test="open-private-field"]').trigger('click');
|
await wrapper.get('[data-test="private-field-name"]').setValue('临时备注');
|
await wrapper.get('[data-test="private-field-submit"]').trigger('click');
|
expect(wrapper.emitted('create-private')?.[0]?.[0]).toMatchObject({ ziduanMingcheng: '临时备注', shujuLeixing: 'TEXT' });
|
});
|
|
it('打开本模板字段页面时使用当前选中文本预填字段名称', async () => {
|
const readSelectedText = vi.fn(async () => ' 是否\n 合格 ');
|
const wrapper = mount(FieldEditor, {
|
props: { fields, readSelectedText },
|
global: { plugins: [Antd] },
|
});
|
|
await wrapper.get('[data-test="open-private-field"]').trigger('click');
|
await vi.waitFor(() => expect(wrapper.find('[data-test="private-field-form"]').exists()).toBe(true));
|
|
expect(readSelectedText).toHaveBeenCalledOnce();
|
expect(wrapper.get('[data-test="private-field-name"]').attributes('value')).toBe('是否 合格');
|
});
|
|
it('读取选中文本失败时打开空表单并保留字段名称必填校验', async () => {
|
const readSelectedText = vi.fn(async () => {
|
throw new Error('selection unavailable');
|
});
|
const wrapper = mount(FieldEditor, {
|
props: { fields, readSelectedText },
|
global: { plugins: [Antd] },
|
});
|
|
await wrapper.get('[data-test="open-private-field"]').trigger('click');
|
await vi.waitFor(() => expect(wrapper.find('[data-test="private-field-form"]').exists()).toBe(true));
|
expect(wrapper.get('[data-test="private-field-name"]').attributes('value')).toBe('');
|
|
await wrapper.get('[data-test="private-field-submit"]').trigger('click');
|
expect(wrapper.emitted('create-private')).toBeUndefined();
|
expect(wrapper.get('[data-test="private-field-error"]').text()).toContain('请输入字段名称');
|
});
|
|
it('创建本模板复选框时复用当前步骤选择组候选', async () => {
|
const wrapper = mount(FieldEditor, {
|
props: {
|
bindings: [checkboxBinding('passed', 'inspection_result', 'step-1'), checkboxBinding('other', 'other_step_group', 'step-2')],
|
currentStepId: 'step-1',
|
fields,
|
},
|
global: { plugins: [Antd] },
|
});
|
await wrapper.get('[data-test="open-private-field"]').trigger('click');
|
await wrapper.get('[data-test="private-field-name"]').setValue('是否复核');
|
wrapper.getComponent({ name: 'ASelect' }).vm.$emit('update:value', 'CHECKBOX');
|
await wrapper.vm.$nextTick();
|
|
const groupInput = wrapper.getComponent({ name: 'AAutoComplete' });
|
expect(groupInput.props('options')).toEqual([{ label: 'inspection_result(1 个成员)', value: 'inspection_result' }]);
|
groupInput.vm.$emit('update:value', 'inspection_result');
|
await wrapper.get('[data-test="private-field-submit"]').trigger('click');
|
|
expect(wrapper.emitted('create-private')?.[0]?.[0]).toMatchObject({
|
checkboxGroupCode: 'inspection_result',
|
shujuLeixing: 'CHECKBOX',
|
});
|
});
|
|
it('用紧凑列表选择引用字段,并在固定详情区展示精确信息', async () => {
|
const second = {
|
...crossTemplateCandidates[0]!,
|
sourceFieldCode: 'temperature',
|
sourceFieldName: '环境温度',
|
sourceFieldType: 'TEXT' as const,
|
sourceFieldVersionId: 'temperature-version-3',
|
sourceTemplateCode: 'ENV-TPL-002',
|
sourceTemplateId: 'environment-template',
|
};
|
const wrapper = mount(FieldEditor, {
|
props: {
|
crossTemplateCandidates: [...crossTemplateCandidates, second],
|
crossTemplateSourceTemplates,
|
fields,
|
},
|
global: { plugins: [Antd] },
|
});
|
|
const openCrossTemplate = wrapper.get('[data-test="open-cross-template"]');
|
expect(openCrossTemplate.text()).toBe('引用其他模板字段');
|
expect(openCrossTemplate.find('.anticon-link').exists()).toBe(true);
|
await openCrossTemplate.trigger('click');
|
|
expect(wrapper.emitted('load-cross-template-templates')).toEqual([[]]);
|
expect(wrapper.text()).toContain('引用其他模板字段');
|
expect(wrapper.getComponent({ name: 'AEmpty' }).classes()).toContain('content-empty');
|
const templateSelect = wrapper.getComponent({ name: 'ASelect' });
|
expect(templateSelect.props('allowClear')).toBe(true);
|
expect(templateSelect.props('options')).toEqual([
|
{ label: 'SOURCE-TPL-001', value: 'source-template' },
|
{ label: 'ENV-TPL-002', value: 'environment-template' },
|
]);
|
expect(wrapper.get('[data-test="cross-template-search"]').attributes('disabled')).toBeDefined();
|
|
templateSelect.vm.$emit('update:value', 'source-template');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.emitted('load-cross-template-fields')).toEqual([['source-template']]);
|
expect(wrapper.get('[data-test="cross-template-count"]').text()).toContain('1');
|
const options = wrapper.findAll('[data-test="cross-template-option"]');
|
expect(options).toHaveLength(1);
|
expect(options[0]!.get('[data-test="cross-template-field-name"]').text()).toBe('来源检验结果');
|
expect(options[0]!.get('[data-test="cross-template-source-template"]').text()).toBe('SOURCE-TPL-001');
|
expect(options[0]!.get('[data-test="cross-template-type"]').text()).toBe('数值');
|
expect(options[0]!.find('.ant-tag').exists()).toBe(false);
|
expect(options[0]!.get('[data-test="cross-template-type"]').classes()).toContain('cross-template-type--decimal');
|
expect(options[0]!.text()).not.toContain('source_result');
|
expect(options[0]!.text()).not.toContain('source-version-2');
|
|
expect(wrapper.find('[data-test="cross-template-details"]').exists()).toBe(false);
|
expect(wrapper.get('[data-test="submit-cross-template"]').attributes('disabled')).toBeDefined();
|
wrapper.getComponent({ name: 'ARadioGroup' }).vm.$emit('update:value', 'source-template::source-version-2');
|
await wrapper.vm.$nextTick();
|
|
const details = wrapper.get('[data-test="cross-template-details"]');
|
expect(details.text()).toContain('SOURCE-TPL-001');
|
expect(details.get('[data-test="cross-template-detail-code"]').text()).toBe('source_result');
|
expect(details.get('[data-test="cross-template-detail-version"]').text()).toBe('source-version-2');
|
const detailValues = details.findAll('[data-test="cross-template-detail-value"]');
|
expect(detailValues).toHaveLength(3);
|
expect(detailValues.map((value) => value.attributes('title'))).toEqual(['SOURCE-TPL-001', 'source_result', 'source-version-2']);
|
expect(wrapper.get('[data-test="cross-template-list"]').classes()).toContain('cross-template-list');
|
expect(wrapper.get('[data-test="cross-template-footer"]').classes()).toContain('cross-template-footer');
|
|
templateSelect.vm.$emit('update:value', 'environment-template');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.emitted('load-cross-template-fields')).toEqual([['source-template'], ['environment-template']]);
|
expect(wrapper.find('[data-test="cross-template-details"]').exists()).toBe(false);
|
wrapper.getComponent({ name: 'ARadioGroup' }).vm.$emit('update:value', 'environment-template::temperature-version-3');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.get('[data-test="cross-template-details"]').text()).toContain('ENV-TPL-002');
|
expect(wrapper.get('[data-test="cross-template-detail-code"]').text()).toBe('temperature');
|
expect(wrapper.get('[data-test="cross-template-detail-version"]').text()).toBe('temperature-version-3');
|
|
await wrapper.get('[data-test="submit-cross-template"]').trigger('click');
|
expect(wrapper.emitted('submit-cross-template')).toEqual([[second]]);
|
|
templateSelect.vm.$emit('update:value', undefined);
|
await wrapper.vm.$nextTick();
|
expect(wrapper.find('[data-test="cross-template-option"]').exists()).toBe(false);
|
expect(wrapper.find('[data-test="cross-template-details"]').exists()).toBe(false);
|
});
|
|
it('按字段名称、编码或版本筛选所选模板候选', async () => {
|
const second = {
|
...crossTemplateCandidates[0]!,
|
sourceFieldCode: 'temperature',
|
sourceFieldName: '环境温度',
|
sourceFieldVersionId: 'temperature-version-3',
|
};
|
const wrapper = mount(FieldEditor, {
|
props: {
|
crossTemplateCandidates: [...crossTemplateCandidates, second],
|
crossTemplateSourceTemplates,
|
fields,
|
},
|
global: { plugins: [Antd] },
|
});
|
|
await wrapper.get('[data-test="open-cross-template"]').trigger('click');
|
wrapper.getComponent({ name: 'ASelect' }).vm.$emit('update:value', 'source-template');
|
await wrapper.vm.$nextTick();
|
await wrapper.get('[data-test="cross-template-search"]').setValue('temperature-version');
|
|
expect(wrapper.text()).toContain('环境温度');
|
expect(wrapper.text()).not.toContain('来源检验结果');
|
});
|
|
it('相同全局字段版本来自不同模板时仍可精确选择来源模板', async () => {
|
const second = {
|
...crossTemplateCandidates[0]!,
|
sourceTemplateCode: 'SOURCE-TPL-002',
|
sourceTemplateId: 'source-template-2',
|
};
|
const wrapper = mount(FieldEditor, {
|
props: { crossTemplateCandidates: [], crossTemplateSourceTemplates, fields },
|
global: { plugins: [Antd] },
|
});
|
|
await wrapper.get('[data-test="open-cross-template"]').trigger('click');
|
wrapper.getComponent({ name: 'ASelect' }).vm.$emit('update:value', 'source-template-2');
|
await wrapper.setProps({ crossTemplateCandidates: [second] });
|
await wrapper.vm.$nextTick();
|
wrapper.getComponent({ name: 'ARadioGroup' }).vm.$emit('update:value', 'source-template-2::source-version-2');
|
await wrapper.vm.$nextTick();
|
await wrapper.get('[data-test="submit-cross-template"]').trigger('click');
|
|
expect(wrapper.emitted('submit-cross-template')).toEqual([[second]]);
|
});
|
|
it('disabled 时不能选择、创建或提交', () => {
|
const wrapper = mountEditor(true);
|
expect(wrapper.findComponent(Select).props('disabled')).toBe(true);
|
expect(wrapper.get('[data-test="open-private-field"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
|
});
|
});
|