import { mount, shallowMount } from '@vue/test-utils';
|
import { defineComponent, h } from 'vue';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import TemplatePage from '../index.vue';
|
import pageSource from '../index.vue?raw';
|
|
const apiMocks = vi.hoisted(() => ({
|
getTemplateCategoryTree: vi.fn(),
|
getTemplateDirectory: vi.fn(),
|
}));
|
const localFormOpenMock = vi.hoisted(() => vi.fn());
|
const messageMocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), warning: vi.fn() }));
|
const formState = vi.hoisted(() => ({ resetFields: vi.fn(), values: {} as Record<string, unknown> }));
|
const tableState = vi.hoisted(() => ({
|
actions: [] as any[],
|
config: undefined as any,
|
reload: vi.fn(),
|
row: {
|
bizEnabled: 'enabled',
|
bizStatus: 'PUBLISHED',
|
mubanBianhao: 'TMP-001',
|
shengxiaoMubanBanben: 'R01',
|
shengxiaoMubanId: 'template-1',
|
} as any,
|
}));
|
const modalState = vi.hoisted(() => ({
|
opened: [] as Array<{ data?: any; key: string }>,
|
}));
|
|
const ButtonStub = defineComponent({
|
name: 'AButton',
|
emits: ['click'],
|
setup(_, { emit, slots }) {
|
return () => h('button', { onClick: () => emit('click'), type: 'button' }, slots.default?.());
|
},
|
});
|
vi.mock('#/api/x/eln/templates', () => ({
|
getTemplateCategoryTree: apiMocks.getTemplateCategoryTree,
|
getTemplateDirectory: apiMocks.getTemplateDirectory,
|
}));
|
|
vi.mock('@jnpf/ui', async () => {
|
const { defineComponent, h } = await import('vue');
|
return {
|
BasicLeftTree: defineComponent({
|
name: 'BasicLeftTree',
|
props: {
|
fieldNames: { default: () => ({}), type: Object },
|
loading: Boolean,
|
showReset: Boolean,
|
title: { default: '', type: String },
|
treeData: { default: () => [], type: Array },
|
},
|
emits: ['reload', 'reset', 'select'],
|
setup(props) {
|
return () => h('div', { 'data-test': 'template-category-tree' }, JSON.stringify(props.treeData));
|
},
|
}),
|
};
|
});
|
|
vi.mock('#/components/GlobalFormModal/index.vue', async () => {
|
const { defineComponent, h } = await import('vue');
|
return {
|
default: defineComponent({
|
name: 'GlobalFormModal',
|
props: { listenGlobal: Boolean },
|
setup(_, { expose }) {
|
expose({ open: localFormOpenMock });
|
return () => h('div', { 'data-test': 'local-form-modal' });
|
},
|
}),
|
};
|
});
|
|
vi.mock('../VersionDrawer.vue', async () => {
|
const { defineComponent, h } = await import('vue');
|
return { default: defineComponent({ name: 'VersionDrawer', setup: () => () => h('div') }) };
|
});
|
|
vi.mock('@jnpf/hooks', () => ({
|
useMessage: () => ({ createMessage: messageMocks }),
|
}));
|
|
vi.mock('@jnpf/ui/form', () => ({
|
BasicForm: { name: 'BasicForm', template: '<div />' },
|
useForm: () => [vi.fn(), { getFieldsValue: () => formState.values, resetFields: formState.resetFields }],
|
}));
|
|
vi.mock('@jnpf/ui/drawer', () => ({
|
useDrawer: () => [vi.fn(), { openDrawer: (open: boolean, data?: any) => open && modalState.opened.push({ data, key: 'versionList' }) }],
|
}));
|
|
vi.mock('@jnpf/ui/vxeTable', async () => {
|
const { defineComponent, h } = await import('vue');
|
return {
|
BasicVxeTable: defineComponent({
|
name: 'BasicVxeTable',
|
setup(_, { slots }) {
|
return () =>
|
h('div', [
|
slots.tableTitle?.(),
|
slots.jianyanXiangmu?.({ record: tableState.row }),
|
slots.mubanFenlei?.({ record: tableState.row }),
|
slots.shengxiaoMubanBanben?.({ record: tableState.row }),
|
slots.action?.({ record: tableState.row }),
|
]);
|
},
|
}),
|
TableAction: defineComponent({
|
name: 'TableAction',
|
props: { actions: Array },
|
setup(props) {
|
tableState.actions = props.actions as any[];
|
return () => h('div');
|
},
|
}),
|
useVxeTable: (config: any) => {
|
tableState.config = config;
|
return [vi.fn(), { reload: tableState.reload }];
|
},
|
};
|
});
|
|
describe('eLN template page', () => {
|
beforeEach(() => {
|
vi.clearAllMocks();
|
localFormOpenMock.mockResolvedValue(undefined);
|
tableState.actions = [];
|
tableState.row = {
|
bizEnabled: 'enabled',
|
bizStatus: 'PUBLISHED',
|
mubanBianhao: 'TMP-001',
|
shengxiaoMubanBanben: 'R01',
|
shengxiaoMubanId: 'template-1',
|
};
|
modalState.opened = [];
|
formState.values = {};
|
apiMocks.getTemplateDirectory.mockResolvedValue({ data: { list: [], pagination: { currentPage: 1, pageSize: 20, total: 0 } } });
|
apiMocks.getTemplateCategoryTree.mockResolvedValue({ data: [{ f_id: 'category-1', fenlei_mingcheng: '检验记录' }] });
|
});
|
|
it('加载电子记录分类树', async () => {
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
|
await vi.waitFor(() => expect(apiMocks.getTemplateCategoryTree).toHaveBeenCalledOnce());
|
const tree = wrapper.findComponent({ name: 'BasicLeftTree' });
|
await vi.waitFor(() => expect(tree.props('treeData')).toEqual([{ f_id: 'category-1', fenlei_mingcheng: '检验记录' }]));
|
expect(tree.props()).toMatchObject({
|
fieldNames: { children: 'children', key: 'f_id', title: 'fenlei_mingcheng' },
|
showReset: true,
|
title: '电子记录分类',
|
treeData: [{ f_id: 'category-1', fenlei_mingcheng: '检验记录' }],
|
});
|
});
|
|
it('兼容分类树接口返回 JSON 字符串', async () => {
|
apiMocks.getTemplateCategoryTree.mockResolvedValueOnce({
|
data: JSON.stringify([{ f_id: 'category-2', fenlei_mingcheng: '原始记录' }]),
|
});
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
const tree = wrapper.findComponent({ name: 'BasicLeftTree' });
|
|
await vi.waitFor(() => expect(tree.props('treeData')).toEqual([{ f_id: 'category-2', fenlei_mingcheng: '原始记录' }]));
|
});
|
|
it('刷新分类树时重新请求分类数据', async () => {
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
const tree = wrapper.findComponent({ name: 'BasicLeftTree' });
|
|
await vi.waitFor(() => expect(apiMocks.getTemplateCategoryTree).toHaveBeenCalledOnce());
|
tree.vm.$emit('reload');
|
|
await vi.waitFor(() => expect(apiMocks.getTemplateCategoryTree).toHaveBeenCalledTimes(2));
|
});
|
|
it('点击分类后与当前搜索条件组合查询并回到第一页', async () => {
|
formState.values = { bizEnabled: 'enabled', bizStatus: 'PUBLISHED', keyword: '无菌' };
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
|
wrapper.findComponent({ name: 'BasicLeftTree' }).vm.$emit('select', 'category-1');
|
|
expect(tableState.reload).toHaveBeenCalledWith({
|
page: 1,
|
searchInfo: { bizEnabled: 'enabled', bizStatus: 'PUBLISHED', keyword: '无菌', mubanFenlei: 'category-1' },
|
});
|
});
|
|
it('重置分类树时清空分类并保留搜索条件', () => {
|
formState.values = { bizEnabled: 'enabled', keyword: '无菌' };
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
const tree = wrapper.findComponent({ name: 'BasicLeftTree' });
|
tree.vm.$emit('select', 'category-1');
|
tableState.reload.mockClear();
|
|
tree.vm.$emit('reset');
|
|
expect(tableState.reload).toHaveBeenCalledWith({
|
page: 1,
|
searchInfo: { bizEnabled: 'enabled', keyword: '无菌' },
|
});
|
});
|
|
it('重置顶部搜索表单时保留当前分类', async () => {
|
formState.values = { bizStatus: 'PUBLISHED', keyword: '无菌' };
|
formState.resetFields.mockImplementationOnce(async () => {
|
formState.values = {};
|
});
|
const wrapper = mount(TemplatePage, {
|
global: {
|
stubs: {
|
'a-button': true,
|
'a-form-item': true,
|
'a-tag': true,
|
'a-textarea': true,
|
VersionModal: true,
|
},
|
},
|
});
|
wrapper.findComponent({ name: 'BasicLeftTree' }).vm.$emit('select', 'category-1');
|
tableState.reload.mockClear();
|
|
wrapper.findComponent({ name: 'BasicForm' }).vm.$emit('reset');
|
|
await vi.waitFor(() =>
|
expect(tableState.reload).toHaveBeenCalledWith({
|
page: 1,
|
searchInfo: { mubanFenlei: 'category-1' },
|
}),
|
);
|
});
|
|
it('uses the low-code page skeleton and required columns without cards', () => {
|
shallowMount(TemplatePage, { global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } } });
|
const titles = tableState.config.columns.map((column: any) => column.title);
|
const testDefinitionColumn = tableState.config.columns.find((column: any) => column.title === '检验项目');
|
const templateCategoryColumn = tableState.config.columns.find((column: any) => column.title === '模板分类');
|
|
expect(pageSource).toContain('<div class="jnpf-content-wrapper">');
|
expect(pageSource).toContain('<BasicForm');
|
expect(pageSource).toContain('<BasicVxeTable');
|
expect(pageSource).toContain('<TableAction');
|
expect(pageSource).not.toContain('<BasicModal');
|
expect(pageSource).not.toContain('a-card');
|
expect(titles).toEqual(['模板编号', '检验项目', '模板分类', '生效版本', '发布状态', '启用状态', '生效时间', '字段数', '更新时间', '创建时间']);
|
expect(testDefinitionColumn.dataIndex).toBe('jianyanXiangmuMingcheng');
|
expect(templateCategoryColumn.dataIndex).toBe('mubanFenleiMingcheng');
|
});
|
|
it('主列表按模板编号请求目录聚合结果', async () => {
|
shallowMount(TemplatePage, { global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } } });
|
const query = tableState.config.beforeFetch({ currentPage: 1, pageSize: 20 });
|
|
await tableState.config.api(query);
|
|
expect(apiMocks.getTemplateDirectory).toHaveBeenCalledWith({ currentPage: 1, pageSize: 20 });
|
});
|
|
it('显示生效版本,无当前版本时显示未生效', () => {
|
const effective = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-tag': true } },
|
});
|
expect(effective.text()).toContain('R01');
|
|
tableState.row = {
|
bizEnabled: undefined,
|
bizStatus: undefined,
|
mubanBianhao: 'TMP-001',
|
shengxiaoMubanBanben: undefined,
|
shengxiaoMubanId: undefined,
|
};
|
const inactive = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-tag': true } },
|
});
|
expect(inactive.text()).toContain('未生效');
|
});
|
|
it('shows the test definition name and falls back to its id', () => {
|
tableState.row = { ...tableState.row, jianyanXiangmu: 'item-1' };
|
const fallback = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
expect(fallback.text()).toContain('item-1');
|
|
tableState.row = { ...tableState.row, jianyanXiangmuMingcheng: '无菌检查' };
|
const resolved = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
expect(resolved.text()).toContain('无菌检查');
|
});
|
|
it('shows the template category name and falls back to its id', () => {
|
tableState.row = { ...tableState.row, mubanFenlei: 'category-1' };
|
const fallback = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
expect(fallback.text()).toContain('category-1');
|
|
tableState.row = { ...tableState.row, mubanFenleiMingcheng: '检验记录' };
|
const resolved = mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
expect(resolved.text()).toContain('检验记录');
|
});
|
|
it('主列表只保留模板目录级操作', () => {
|
mount(TemplatePage, {
|
global: { stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
|
expect(tableState.actions.map((action) => action.label)).toEqual(['版本']);
|
});
|
|
it('从操作列打开指定模板的版本列表', async () => {
|
mount(TemplatePage, {
|
global: {
|
stubs: { 'a-button': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionDrawer: true, VersionModal: true },
|
},
|
});
|
|
const versions = tableState.actions.find((action) => action.label === '版本');
|
expect(versions).toBeTruthy();
|
await versions.onClick();
|
|
expect(modalState.opened).toContainEqual({
|
data: { mubanBianhao: 'TMP-001' },
|
key: 'versionList',
|
});
|
});
|
|
it('在列表内容区使用低代码平台默认容器打开新增表单', async () => {
|
const wrapper = mount(TemplatePage, {
|
global: { stubs: { 'a-button': ButtonStub, 'a-form-item': true, 'a-tag': true, 'a-textarea': true, HealthDrawer: true, VersionModal: true } },
|
});
|
const localForm = wrapper.findComponent({ name: 'GlobalFormModal' });
|
|
expect(localForm.exists()).toBe(true);
|
expect(localForm.props('listenGlobal')).toBe(false);
|
expect(localForm.element.closest('.jnpf-content-wrapper')).not.toBeNull();
|
|
const addButton = wrapper.findAll('button').find((button) => button.text() === '新增模板');
|
expect(addButton).toBeTruthy();
|
await addButton!.trigger('click');
|
|
expect(localFormOpenMock).toHaveBeenCalledWith({
|
modelId: '854727805680747141',
|
onConfirm: expect.any(Function),
|
title: '新增电子记录模板',
|
});
|
expect(pageSource).not.toMatch(/type: '(?:modal|drawer|fullScreen)'/);
|
});
|
});
|