/* @vitest-environment happy-dom */
|
|
import type { VueWrapper } from '@vue/test-utils';
|
import type { Component } from 'vue';
|
|
import { mount } from '@vue/test-utils';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { defineComponent, getCurrentInstance, h, inject, nextTick, provide } from 'vue';
|
|
const modalMocks = vi.hoisted(() => ({
|
auditPrint: vi.fn(),
|
closeModal: vi.fn(),
|
createConfirm: vi.fn(),
|
downloadPdf: vi.fn(),
|
openModal: vi.fn(),
|
print: vi.fn(),
|
register: vi.fn(),
|
reload: vi.fn(),
|
browserPrint: vi.fn(),
|
}));
|
|
vi.mock('#/router', () => ({
|
router: {
|
push: vi.fn(),
|
},
|
}));
|
|
vi.mock('@jnpf/hooks', () => ({
|
useGlobSetting: () => ({ apiURL: '' }),
|
useMessage: () => ({
|
createConfirm: modalMocks.createConfirm,
|
}),
|
}));
|
|
vi.mock('@jnpf/ui/modal', async () => {
|
const { defineComponent, h, onMounted } = await import('vue');
|
|
return {
|
BasicModal: defineComponent({
|
name: 'BasicModal',
|
emits: ['register'],
|
props: {
|
closeFunc: Function,
|
},
|
setup(_, { emit, slots }) {
|
onMounted(() => emit('register'));
|
|
return () =>
|
h('section', { 'data-test': 'basic-modal' }, [
|
h('header', { 'data-test': 'modal-title' }, slots.title?.()),
|
h('main', { 'data-test': 'modal-content' }, slots.default?.()),
|
]);
|
},
|
}),
|
useModal: () => [modalMocks.register, { closeModal: modalMocks.closeModal, openModal: modalMocks.openModal }],
|
};
|
});
|
|
vi.mock('ant-design-vue', async () => {
|
const { defineComponent, h } = await import('vue');
|
|
return {
|
Button: defineComponent({
|
name: 'AButton',
|
props: {
|
danger: Boolean,
|
disabled: Boolean,
|
loading: Boolean,
|
type: String,
|
},
|
emits: ['click'],
|
setup(props, { emit, slots }) {
|
return () =>
|
h(
|
'button',
|
{
|
disabled: props.disabled || props.loading,
|
type: 'button',
|
onClick: () => emit('click'),
|
},
|
slots.default?.(),
|
);
|
},
|
}),
|
Space: defineComponent({
|
name: 'ASpace',
|
setup(_, { slots }) {
|
return () => h('div', { 'data-test': 'space' }, slots.default?.());
|
},
|
}),
|
Spin: defineComponent({
|
name: 'ASpin',
|
setup(_, { slots }) {
|
return () => h('div', slots.default?.());
|
},
|
}),
|
};
|
});
|
|
vi.mock('#/components/CustomPrint/CustomPrintPreviewPane.vue', () => ({
|
default: defineComponent({
|
name: 'CustomPrintPreviewPane',
|
emits: ['error', 'loaded', 'loading'],
|
props: {
|
config: {
|
required: true,
|
type: Object,
|
},
|
},
|
setup(_, { expose }) {
|
expose({
|
browserPrint: modalMocks.browserPrint,
|
downloadPdf: modalMocks.downloadPdf,
|
hiprint: modalMocks.print,
|
reload: modalMocks.reload,
|
});
|
|
return () => h('div', { 'data-test': 'custom-print-preview' });
|
},
|
}),
|
}));
|
|
vi.mock('../ReportAttachmentsPane.vue', () => ({
|
default: defineComponent({
|
name: 'ReportAttachmentsPane',
|
setup() {
|
return () => h('div', { 'data-test': 'attachments-pane' });
|
},
|
}),
|
}));
|
|
vi.mock('../ReportFormDetailPane.vue', () => ({
|
default: defineComponent({
|
name: 'ReportFormDetailPane',
|
props: {
|
tab: {
|
required: true,
|
type: Object,
|
},
|
},
|
setup(props) {
|
return () => h('div', { 'data-test': 'form-detail-pane' }, props.tab.id);
|
},
|
}),
|
}));
|
|
vi.mock('../ReportElnRecordsPane.vue', () => ({
|
default: defineComponent({
|
name: 'ReportElnRecordsPane',
|
props: {
|
tab: {
|
required: true,
|
type: Object,
|
},
|
},
|
setup(props) {
|
return () => h('div', { 'data-test': 'eln-records-pane' }, props.tab.qingyandanId);
|
},
|
}),
|
}));
|
|
import { onlineUtils } from '#/utils/jnpf';
|
|
import GlobalReportViewModal from '../index.vue';
|
|
const customPrintTab = {
|
key: 'customPrint',
|
print: {
|
data: { id: 'record-id' },
|
template: 'template-id',
|
},
|
} as const;
|
|
function mountModal() {
|
return mount(GlobalReportViewModal, {
|
global: {
|
components: {
|
'a-tab-pane': defineComponent({
|
name: 'ATabPane',
|
props: {
|
tab: String,
|
},
|
setup(props) {
|
const instance = getCurrentInstance();
|
const setActiveKey = inject<(key: string) => void>('setActiveKey');
|
|
return () =>
|
h(
|
'div',
|
{
|
'data-test': 'tab-pane',
|
onClick: () => setActiveKey?.(String(instance?.vnode.key)),
|
},
|
props.tab,
|
);
|
},
|
}),
|
'a-tabs': defineComponent({
|
name: 'ATabs',
|
emits: ['update:activeKey'],
|
props: {
|
activeKey: String,
|
},
|
setup(_, { emit, slots }) {
|
provide('setActiveKey', (key: string) => emit('update:activeKey', key));
|
|
return () => h('div', { 'data-test': 'tabs' }, slots.default?.());
|
},
|
}),
|
},
|
},
|
});
|
}
|
|
async function openReportView(config = {}) {
|
onlineUtils.openReportView({
|
tabs: [customPrintTab],
|
title: '查看报告',
|
...config,
|
});
|
|
await nextTick();
|
await nextTick();
|
}
|
|
async function emitPrintLoaded(wrapper: VueWrapper) {
|
wrapper.findComponent({ name: 'CustomPrintPreviewPane' }).vm.$emit('loaded');
|
await nextTick();
|
}
|
|
function getButton(wrapper: VueWrapper, text: string) {
|
const button = wrapper.findAll('button').find((item) => item.text() === text);
|
|
expect(button, `button "${text}" exists`).toBeTruthy();
|
return button!;
|
}
|
|
function queryButton(wrapper: VueWrapper, text: string) {
|
return wrapper.findAll('button').find((item) => item.text() === text);
|
}
|
|
function getFormDetailText(wrapper: VueWrapper) {
|
return wrapper.find('[data-test="form-detail-pane"]').text();
|
}
|
|
describe('GlobalReportViewModal interactions', () => {
|
let wrapper: VueWrapper<Component> | null = null;
|
|
beforeEach(() => {
|
modalMocks.auditPrint.mockClear();
|
modalMocks.closeModal.mockClear();
|
modalMocks.createConfirm.mockClear();
|
modalMocks.downloadPdf.mockReset();
|
modalMocks.openModal.mockClear();
|
modalMocks.print.mockImplementation((options) => {
|
options?.onPrint?.({ mode: 'hiprint' });
|
return Promise.resolve({ mode: 'hiprint' });
|
});
|
modalMocks.register.mockClear();
|
modalMocks.reload.mockClear();
|
modalMocks.browserPrint.mockClear();
|
vi.spyOn(onlineUtils, 'auditPrint').mockImplementation(modalMocks.auditPrint);
|
wrapper = mountModal();
|
});
|
|
afterEach(() => {
|
wrapper?.unmount();
|
wrapper = null;
|
vi.restoreAllMocks();
|
});
|
|
it('无 actions 时显示默认打印按钮和关闭按钮', async () => {
|
await openReportView();
|
await emitPrintLoaded(wrapper!);
|
|
expect(getButton(wrapper!, '导出PDF').attributes('disabled')).toBeUndefined();
|
expect(getButton(wrapper!, '打印').attributes('disabled')).toBeUndefined();
|
expect(getButton(wrapper!, '关闭').exists()).toBe(true);
|
});
|
|
it('有 actions 时显示业务动作且不显示默认打印按钮', async () => {
|
await openReportView({
|
actions: [
|
{
|
key: 'approve',
|
label: '审核通过',
|
onClick: vi.fn(),
|
type: 'primary',
|
},
|
],
|
});
|
|
expect(getButton(wrapper!, '审核通过').exists()).toBe(true);
|
expect(queryButton(wrapper!, '导出PDF')).toBeUndefined();
|
expect(queryButton(wrapper!, '打印')).toBeUndefined();
|
expect(getButton(wrapper!, '关闭').exists()).toBe(true);
|
});
|
|
it('actions 为空数组时隐藏默认打印按钮', async () => {
|
await openReportView({
|
actions: [],
|
});
|
|
expect(queryButton(wrapper!, '导出PDF')).toBeUndefined();
|
expect(queryButton(wrapper!, '打印')).toBeUndefined();
|
expect(getButton(wrapper!, '关闭').exists()).toBe(true);
|
});
|
|
it('action 没有 confirmText 时点击后直接执行 action 且不弹确认', async () => {
|
const onClick = vi.fn();
|
|
await openReportView({
|
actions: [
|
{
|
key: 'approve',
|
label: '审核通过',
|
onClick,
|
},
|
],
|
});
|
|
await getButton(wrapper!, '审核通过').trigger('click');
|
|
expect(modalMocks.createConfirm).not.toHaveBeenCalled();
|
expect(onClick).toHaveBeenCalledTimes(1);
|
});
|
|
it('action 有 confirmText 时点击后弹出确认且不直接执行 action', async () => {
|
const onClick = vi.fn();
|
|
await openReportView({
|
actions: [
|
{
|
confirmText: '确认审核通过吗?',
|
key: 'approve',
|
label: '审核通过',
|
onClick,
|
},
|
],
|
});
|
|
await getButton(wrapper!, '审核通过').trigger('click');
|
|
expect(modalMocks.createConfirm).toHaveBeenCalledTimes(1);
|
expect(modalMocks.createConfirm).toHaveBeenCalledWith(
|
expect.objectContaining({
|
content: '确认审核通过吗?',
|
}),
|
);
|
expect(onClick).not.toHaveBeenCalled();
|
|
const confirmOptions = modalMocks.createConfirm.mock.calls[0]?.[0] as { onOk: () => void } | undefined;
|
expect(confirmOptions).toBeTruthy();
|
confirmOptions!.onOk();
|
await nextTick();
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
});
|
|
it('点击打印时记录审计,Hiprint 导出PDF不重复记录', async () => {
|
await openReportView();
|
await emitPrintLoaded(wrapper!);
|
|
await getButton(wrapper!, '打印').trigger('click');
|
expect(modalMocks.print).toHaveBeenCalledTimes(1);
|
expect(modalMocks.auditPrint).toHaveBeenCalledWith({
|
bizType: undefined,
|
printName: '报告',
|
recordTitle: '报告',
|
source: 'REPORT_VIEW',
|
targetTable: undefined,
|
targets: [{ id: 'record-id' }],
|
templateId: undefined,
|
});
|
|
modalMocks.downloadPdf.mockResolvedValue(true);
|
await getButton(wrapper!, '导出PDF').trigger('click');
|
await nextTick();
|
expect(modalMocks.downloadPdf).toHaveBeenCalledWith('查看报告');
|
expect(modalMocks.browserPrint).not.toHaveBeenCalled();
|
expect(modalMocks.print).toHaveBeenCalledTimes(1);
|
expect(modalMocks.auditPrint).toHaveBeenCalledTimes(1);
|
});
|
|
it('Hiprint PDF 导出不可用时回退到浏览器打印且不记录审计', async () => {
|
await openReportView();
|
await emitPrintLoaded(wrapper!);
|
modalMocks.downloadPdf.mockResolvedValue(false);
|
|
await getButton(wrapper!, '导出PDF').trigger('click');
|
await nextTick();
|
|
expect(modalMocks.downloadPdf).toHaveBeenCalledWith('查看报告');
|
expect(modalMocks.browserPrint).toHaveBeenCalledTimes(1);
|
expect(modalMocks.auditPrint).not.toHaveBeenCalled();
|
});
|
|
it('customPrint 支持显式审计字段映射,也可关闭审计', async () => {
|
await openReportView({
|
tabs: [
|
{
|
key: 'customPrint',
|
print: {
|
audit: {
|
bizCodeField: 'report.code',
|
bizType: 'SAMPLE_REPORT',
|
idField: 'report.id',
|
printName: '检验报告',
|
recordTitle: '报告打印',
|
targetTable: 'lims_sample_report',
|
templateId: 'template-id',
|
titleField: 'report.title',
|
},
|
data: { report: { code: 'BG-001', id: 'record-1', title: '第一份报告' } },
|
template: 'template-id',
|
},
|
},
|
],
|
});
|
await emitPrintLoaded(wrapper!);
|
|
await getButton(wrapper!, '打印').trigger('click');
|
|
expect(modalMocks.auditPrint).toHaveBeenCalledWith({
|
bizType: 'SAMPLE_REPORT',
|
printName: '检验报告',
|
recordTitle: '报告打印',
|
source: 'REPORT_VIEW',
|
targetTable: 'lims_sample_report',
|
targets: [{ bizCode: 'BG-001', id: 'record-1', title: '第一份报告' }],
|
templateId: 'template-id',
|
});
|
|
await openReportView({
|
tabs: [{ key: 'customPrint', print: { audit: false, data: { id: 'record-2' }, template: 'template-id' } }],
|
});
|
await emitPrintLoaded(wrapper!);
|
modalMocks.auditPrint.mockClear();
|
|
await getButton(wrapper!, '打印').trigger('click');
|
|
expect(modalMocks.auditPrint).not.toHaveBeenCalled();
|
});
|
|
it('打印渲染数据没有主键时使用同一报告的 formDetail id', async () => {
|
await openReportView({
|
activeKey: 'customPrint',
|
tabs: [
|
{
|
id: 'business-record-id',
|
key: 'formDetail',
|
modelId: 'model-id',
|
title: '报告详情',
|
},
|
{
|
key: 'customPrint',
|
print: {
|
data: { reportBody: { title: '稳定性考察报告' } },
|
template: 'wendingxing_huizong',
|
title: '汇总',
|
},
|
title: '查看报告',
|
},
|
],
|
});
|
await emitPrintLoaded(wrapper!);
|
|
await getButton(wrapper!, '打印').trigger('click');
|
|
expect(modalMocks.auditPrint).toHaveBeenCalledWith(
|
expect.objectContaining({
|
printName: '汇总',
|
source: 'REPORT_VIEW',
|
targets: [{ id: 'business-record-id' }],
|
}),
|
);
|
});
|
|
it('onlineUtils.closeReportView 连续触发时只关闭一次并只调用一次 onClose', async () => {
|
const onClose = vi.fn();
|
|
await openReportView({ onClose });
|
|
onlineUtils.closeReportView();
|
onlineUtils.closeReportView();
|
|
expect(modalMocks.closeModal).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
});
|
|
it('点击关闭按钮后再次触发 closeReportView 仍只关闭一次', async () => {
|
const onClose = vi.fn();
|
|
await openReportView({ onClose });
|
|
await getButton(wrapper!, '关闭').trigger('click');
|
onlineUtils.closeReportView();
|
|
expect(modalMocks.closeModal).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
});
|
|
it('unmount 后清理 report view 事件监听', async () => {
|
await openReportView();
|
|
expect(modalMocks.openModal).toHaveBeenCalledTimes(1);
|
|
wrapper!.unmount();
|
wrapper = null;
|
|
onlineUtils.openReportView({
|
tabs: [customPrintTab],
|
title: '卸载后打开',
|
});
|
onlineUtils.closeReportView();
|
|
await nextTick();
|
await nextTick();
|
|
expect(modalMocks.openModal).toHaveBeenCalledTimes(1);
|
expect(modalMocks.closeModal).not.toHaveBeenCalled();
|
});
|
|
it('loaded 前打印按钮 disabled,loaded 后启用', async () => {
|
await openReportView();
|
|
expect(getButton(wrapper!, '打印').attributes('disabled')).toBeDefined();
|
|
await emitPrintLoaded(wrapper!);
|
|
expect(getButton(wrapper!, '打印').attributes('disabled')).toBeUndefined();
|
});
|
|
it('支持在多个 formDetail tab 之间切换', async () => {
|
await openReportView({
|
activeKey: 'formDetail',
|
tabs: [
|
{
|
id: 'form-id-1',
|
key: 'formDetail',
|
modelId: 'model-id-1',
|
title: '委托信息',
|
},
|
{
|
id: 'form-id-2',
|
key: 'formDetail',
|
modelId: 'model-id-2',
|
title: '检验记录',
|
},
|
],
|
});
|
|
expect(wrapper!.findAll('[data-test="tab-pane"]').map((item) => item.text())).toEqual(['委托信息', '检验记录']);
|
expect(getFormDetailText(wrapper!)).toContain('form-id-1');
|
|
await wrapper!.findAll('[data-test="tab-pane"]')[1]!.trigger('click');
|
|
expect(getFormDetailText(wrapper!)).toContain('form-id-2');
|
});
|
|
it('renders the ELN records pane for an elnRecords tab', async () => {
|
await openReportView({
|
tabs: [{ key: 'elnRecords', qingyandanId: 'request-1', title: '电子记录' }],
|
});
|
|
expect(wrapper!.get('[data-test="eln-records-pane"]').text()).toBe('request-1');
|
});
|
});
|