刘光辉
14 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { mount } from '@vue/test-utils';
import Antd from 'ant-design-vue';
import { describe, expect, it } from 'vitest';
 
import DocumentFieldList from '../../src/components/DocumentFieldList.vue';
import type { OfficeContentControl } from '../../src/onlyoffice/types';
 
const controls: OfficeContentControl[] = [
  { Alias: '样品编号', ControlType: 'text', InternalId: 'single-1', Tag: 'eln.field.sample_id' },
  { Alias: '检测结果', ControlType: 'dropdown', InternalId: 'repeat-1', Tag: 'eln.repeat.results.row_01.value' },
  { Alias: '', InternalId: 'other-1', Tag: 'custom.control' },
  { InternalId: '', Tag: '' },
];
 
function mountList(props: { controls?: OfficeContentControl[]; disabled?: boolean; status?: 'initializing' | 'ready' | 'working' | 'error' } = {}) {
  return mount(DocumentFieldList, {
    props: { controls: props.controls ?? [], status: props.status ?? 'ready', disabled: props.disabled },
    global: { plugins: [Antd] },
  });
}
 
describe('DocumentFieldList', () => {
  it('为空列表提供摘要和明确空状态', () => {
    const wrapper = mountList();
 
    expect(wrapper.get('[data-test="control-summary"]').text()).toBe('控件 0,普通 0,表格 0');
    expect(wrapper.get('[data-test="empty-controls"]').text()).toBe('当前文档没有内容控件');
  });
 
  it.each(['initializing', 'working', 'error'] as const)('%s 且列表为空时不下空文档结论', (status) => {
    const wrapper = mountList({ status });
 
    expect(wrapper.find('[data-test="empty-controls"]').exists()).toBe(false);
    expect(wrapper.find('[data-test="empty-search-result"]').exists()).toBe(false);
  });
 
  it('按 Tag 分类并显示字段位置、具体类型与精确摘要', () => {
    const wrapper = mountList({ controls });
 
    expect(wrapper.get('[data-test="control-summary"]').text()).toBe('控件 4,普通 1,表格 1');
    const metadata = wrapper.findAll('[data-test="control-metadata"]').map((item) => item.text());
    expect(metadata).toEqual(['sample_id · 单行文本', '重复表格 · results · row_01 · value · 下拉选择', '非 ELN 内容控件', '非 ELN 内容控件']);
    expect(wrapper.get('[data-test="control-list"]').attributes('role')).toBe('list');
  });
 
  it('显示五种具体类型并为缺失类型的 ELN 控件提供兜底', () => {
    const typedControls: OfficeContentControl[] = [
      { Alias: '文本', ControlType: 'text', InternalId: '1', Tag: 'eln.field.text' },
      { Alias: '下拉', ControlType: 'dropdown', InternalId: '2', Tag: 'eln.field.dropdown' },
      { Alias: '复选', ControlType: 'checkbox', InternalId: '3', Tag: 'eln.field.checkbox' },
      { Alias: '日期', ControlType: 'date', InternalId: '4', Tag: 'eln.field.date' },
      { Alias: '多行', ControlType: 'multiline', InternalId: '5', Tag: 'eln.field.multiline' },
      { Alias: '旧字段', InternalId: '6', Tag: 'eln.field.legacy' },
    ];
 
    const wrapper = mountList({ controls: typedControls });
 
    expect(wrapper.findAll('[data-test="control-metadata"]').map((item) => item.text())).toEqual([
      'text · 单行文本',
      'dropdown · 下拉选择',
      'checkbox · 复选框',
      'date · 日期',
      'multiline · 多行文本',
      'legacy · 未知类型',
    ]);
  });
 
  it('显示 Alias/Tag fallback,缺少 InternalId 的项目不可定位', () => {
    const wrapper = mountList({ controls });
    const items = wrapper.findAll('[data-test="control-item"]');
 
    expect(items[0]!.text()).toContain('样品编号');
    expect(items[0]!.text()).toContain('eln.field.sample_id');
    expect(items[2]!.text()).toContain('未命名字段 3');
    expect(items[3]!.text()).toContain('未命名字段 4');
    expect(items[3]!.text()).toContain('无 Tag');
    expect(items[3]!.attributes('disabled')).toBeDefined();
  });
 
  it.each([
    ['名称', '样品编号', ['样品编号']],
    ['完整 Tag', 'ELN.FIELD.SAMPLE_ID', ['样品编号']],
    ['重复组编码', 'RESULTS', ['检测结果']],
    ['重复行 ID', 'ROW_01', ['检测结果']],
    ['重复字段编码', 'VALUE', ['检测结果']],
    ['具体类型', '下拉选择', ['检测结果']],
  ])('按%s进行不区分大小写的包含搜索', async (_label, keyword, expectedAliases) => {
    const wrapper = mountList({ controls });
 
    await wrapper.get('[data-test="field-search"]').setValue(keyword);
 
    expect(wrapper.findAll('[data-test="control-item"]').map((item) => item.text())).toEqual(
      expectedAliases.map((alias) => expect.stringContaining(alias)),
    );
    expect(wrapper.get('[data-test="control-summary"]').text()).toBe('控件 4,普通 1,表格 1');
  });
 
  it('有内容控件但搜索无匹配时显示明确空状态', async () => {
    const wrapper = mountList({ controls });
 
    await wrapper.get('[data-test="field-search"]').setValue('不存在的字段');
 
    expect(wrapper.find('[data-test="control-list"]').exists()).toBe(false);
    expect(wrapper.get('[data-test="empty-search-result"]').text()).toBe('没有匹配的文档字段');
  });
 
  it('点击项目只发出 select,disabled 时所有项目不可定位', async () => {
    const wrapper = mountList({ controls });
 
    await wrapper.findAll('[data-test="control-item"]')[1]!.trigger('click');
    expect(wrapper.emitted('select')).toEqual([['repeat-1']]);
 
    await wrapper.setProps({ disabled: true });
    expect(wrapper.findAll('[data-test="control-item"]').every((item) => item.attributes('disabled') !== undefined)).toBe(true);
  });
 
  it('只为 ELN 字段提供解除确认,确认后只发出 delete', async () => {
    const wrapper = mountList({ controls });
    const deletes = wrapper.findAll('[data-test="delete-control"]');
 
    expect(deletes).toHaveLength(2);
    expect(deletes[0]!.attributes()).toMatchObject({ 'aria-label': '解除标注:样品编号', title: '解除标注' });
    const confirms = wrapper.findAllComponents({ name: 'APopconfirm' });
    expect(confirms[0]!.props()).toMatchObject({ title: '解除该字段标注并保留内容?', okText: '解除', cancelText: '取消' });
 
    confirms[0]!.vm.$emit('confirm');
    await wrapper.vm.$nextTick();
    expect(wrapper.emitted('delete')).toEqual([['single-1']]);
    expect(wrapper.emitted('select')).toBeUndefined();
  });
 
  it('disabled 和 working 时禁用删除入口', async () => {
    const wrapper = mountList({ controls, disabled: true });
    expect(wrapper.findAll('[data-test="delete-control"]').every((button) => button.attributes('disabled') !== undefined)).toBe(true);
 
    await wrapper.setProps({ disabled: false, status: 'working' });
    expect(wrapper.findAll('[data-test="delete-control"]').every((button) => button.attributes('disabled') !== undefined)).toBe(true);
  });
 
  it('刷新按钮有稳定标识,working 和 disabled 时禁用且只发出 refresh', async () => {
    const wrapper = mountList({ controls });
    const refresh = wrapper.get('[data-test="refresh-controls"]');
 
    expect(refresh.attributes()).toMatchObject({ 'aria-label': '刷新内容控件', title: '刷新内容控件' });
    await refresh.trigger('click');
    expect(wrapper.emitted('refresh')).toHaveLength(1);
 
    await wrapper.setProps({ status: 'working' });
    expect(wrapper.get('[data-test="refresh-controls"]').attributes('disabled')).toBeDefined();
    await wrapper.setProps({ status: 'ready', disabled: true });
    expect(wrapper.get('[data-test="refresh-controls"]').attributes('disabled')).toBeDefined();
  });
});