/* @vitest-environment happy-dom */
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { onlineUtils } from '#/utils/jnpf';
|
import { buildPrintAuditTargets } from '#/utils/printAudit';
|
|
const requestMocks = vi.hoisted(() => ({ post: vi.fn() }));
|
const hiprintMocks = vi.hoisted(() => ({ PrintTemplate: vi.fn(), autoConnect: vi.fn(), socket: { opened: false, stop: vi.fn() } }));
|
const messageMocks = vi.hoisted(() => ({ error: vi.fn() }));
|
|
vi.mock('#/router', () => ({ router: { push: vi.fn() } }));
|
vi.mock('@jnpf/hooks', () => ({
|
useGlobSetting: vi.fn(),
|
useMessage: () => ({ createMessage: { error: messageMocks.error } }),
|
}));
|
vi.mock('vue-plugin-hiprint', () => ({
|
autoConnect: hiprintMocks.autoConnect,
|
hiprint: { PrintTemplate: hiprintMocks.PrintTemplate },
|
hiwebSocket: hiprintMocks.socket,
|
}));
|
vi.mock('#/api/request', () => ({
|
defHttp: {
|
delete: vi.fn(),
|
get: vi.fn(),
|
post: requestMocks.post,
|
put: vi.fn(),
|
},
|
}));
|
|
const targets = [
|
{ bizCode: 'BG-001', id: 'record-1', title: '第一份报告' },
|
{ bizCode: 'BG-002', id: 'record-2', title: '第二份报告' },
|
];
|
|
describe('onlineUtils.auditPrint', () => {
|
beforeEach(() => {
|
vi.clearAllMocks();
|
requestMocks.post.mockResolvedValue({ code: 200 });
|
});
|
|
it('submits one audit request for a batch and returns its correlation id', () => {
|
const requestId = onlineUtils.auditPrint({
|
bizType: 'SAMPLE_REPORT',
|
printName: ' 检验报告 ',
|
source: 'REPORT_VIEW',
|
targetTable: 'lims_sample_report',
|
targets,
|
});
|
|
expect(requestId).toMatch(/^[0-9a-f]{32}$/);
|
expect(requestMocks.post).toHaveBeenCalledTimes(1);
|
expect(requestMocks.post).toHaveBeenCalledWith(
|
{
|
data: {
|
bizType: 'SAMPLE_REPORT',
|
printName: '检验报告',
|
requestId,
|
source: 'REPORT_VIEW',
|
targetTable: 'lims_sample_report',
|
targets,
|
},
|
url: '/api/lims/lims/biz/print/audit',
|
},
|
{ errorMessageMode: 'none' },
|
);
|
});
|
|
it('retains a caller-provided request id for retry idempotency', () => {
|
const requestId = '123e4567-e89b-12d3-a456-426614174000';
|
|
expect(onlineUtils.auditPrint({ printName: '自定义打印', requestId, source: 'CUSTOM_PRINT', targets: [targets[0]!] })).toBe(requestId);
|
expect(requestMocks.post).toHaveBeenCalledTimes(1);
|
expect(requestMocks.post.mock.calls[0]?.[0]?.data.requestId).toBe(requestId);
|
});
|
|
it('does not submit invalid required data', () => {
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
expect(onlineUtils.auditPrint({ printName: ' ', source: 'CUSTOM_PRINT', targets })).toBeUndefined();
|
expect(onlineUtils.auditPrint({ printName: '自定义打印', source: 'CUSTOM_PRINT', targets: [] })).toBeUndefined();
|
expect(requestMocks.post).not.toHaveBeenCalled();
|
|
errorSpy.mockRestore();
|
});
|
|
it('swallows asynchronous audit failures so printing can continue', async () => {
|
const failure = new Error('audit unavailable');
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
requestMocks.post.mockRejectedValue(failure);
|
|
expect(() => onlineUtils.auditPrint({ printName: '自定义打印', source: 'CUSTOM_PRINT', targets: [targets[0]!] })).not.toThrow();
|
await Promise.resolve();
|
|
expect(errorSpy).toHaveBeenCalledWith('[onlineUtils.auditPrint] record failed', failure);
|
errorSpy.mockRestore();
|
});
|
});
|
|
describe('onlineUtils.hiprint', () => {
|
beforeEach(() => {
|
vi.clearAllMocks();
|
hiprintMocks.autoConnect.mockReset();
|
hiprintMocks.socket.opened = false;
|
});
|
|
it('clears a stale socket before reconnecting on print', async () => {
|
const print2 = vi.fn();
|
hiprintMocks.autoConnect.mockImplementation(() => {
|
hiprintMocks.socket.opened = true;
|
});
|
|
await onlineUtils.hiprint({
|
data: { id: 'report-1' },
|
template: { print: vi.fn(), print2 },
|
});
|
|
expect(hiprintMocks.socket.stop).toHaveBeenCalledTimes(1);
|
expect(hiprintMocks.socket.stop.mock.invocationCallOrder[0]).toBeLessThan(hiprintMocks.autoConnect.mock.invocationCallOrder[0]!);
|
expect(print2).toHaveBeenCalledWith({ id: 'report-1' }, { printer: '', styleHandler: undefined });
|
});
|
|
it('reports the print callback only after the Hiprint client confirms success', async () => {
|
hiprintMocks.socket.opened = true;
|
const print = vi.fn();
|
const print2 = vi.fn();
|
const on = vi.fn();
|
const onPrint = vi.fn();
|
const styleHandler = vi.fn(() => '<style></style>');
|
|
await onlineUtils.hiprint({
|
clientOptions: { printer: 'Mac Printer', title: '检验报告' },
|
data: { id: 'report-1' },
|
onPrint,
|
styleHandler,
|
template: { on, print, print2 },
|
});
|
|
expect(print2).toHaveBeenCalledWith({ id: 'report-1' }, { printer: 'Mac Printer', styleHandler, title: '检验报告' });
|
expect(print).not.toHaveBeenCalled();
|
expect(onPrint).not.toHaveBeenCalled();
|
expect(on).toHaveBeenCalledWith('printSuccess', expect.any(Function));
|
expect(on).toHaveBeenCalledWith('printError', expect.any(Function));
|
|
on.mock.calls.find(([eventName]) => eventName === 'printSuccess')?.[1]();
|
|
expect(onPrint).toHaveBeenCalledWith({ mode: 'hiprint' });
|
});
|
|
it('settles each print dispatch only once when the client repeats lifecycle events', async () => {
|
hiprintMocks.socket.opened = true;
|
const on = vi.fn();
|
const onPrint = vi.fn();
|
const onError = vi.fn();
|
|
await onlineUtils.hiprint({
|
data: { id: 'report-1' },
|
onError,
|
onPrint,
|
template: { on, print: vi.fn(), print2: vi.fn() },
|
});
|
|
const printSuccess = on.mock.calls.find(([eventName]) => eventName === 'printSuccess')?.[1];
|
const printError = on.mock.calls.find(([eventName]) => eventName === 'printError')?.[1];
|
printSuccess?.();
|
printSuccess?.();
|
printError?.(new Error('late duplicate error'));
|
|
expect(onPrint).toHaveBeenCalledTimes(1);
|
expect(onPrint).toHaveBeenCalledWith({ mode: 'hiprint' });
|
expect(onError).not.toHaveBeenCalled();
|
expect(messageMocks.error).not.toHaveBeenCalled();
|
});
|
|
it('reports the error callback when the Hiprint client returns a print error', async () => {
|
hiprintMocks.socket.opened = true;
|
const error = { message: 'printer unavailable' };
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const on = vi.fn();
|
const onError = vi.fn();
|
|
await onlineUtils.hiprint({
|
data: { id: 'report-1' },
|
onError,
|
template: { on, print: vi.fn(), print2: vi.fn() },
|
});
|
|
on.mock.calls.find(([eventName]) => eventName === 'printError')?.[1](error);
|
|
expect(onError).toHaveBeenCalledWith(error);
|
expect(messageMocks.error).toHaveBeenCalledWith({ content: '打印失败,请检查打印客户端和打印机后重试', duration: 3, type: 'error' });
|
errorSpy.mockRestore();
|
});
|
|
it('delegates the print error without a duplicate toast in silent mode', async () => {
|
hiprintMocks.socket.opened = true;
|
const error = new Error('printer unavailable');
|
const on = vi.fn();
|
const onError = vi.fn();
|
|
await onlineUtils.hiprint({
|
data: { id: 'report-1' },
|
errorMessageMode: 'none',
|
onError,
|
template: { on, print: vi.fn(), print2: vi.fn() },
|
});
|
on.mock.calls.find(([eventName]) => eventName === 'printError')?.[1](error);
|
|
expect(onError).toHaveBeenCalledWith(error);
|
expect(messageMocks.error).not.toHaveBeenCalled();
|
});
|
|
it('uses printByHtml2 for HTML direct printing and waits for client success', async () => {
|
hiprintMocks.socket.opened = true;
|
const on = vi.fn();
|
const printByHtml2 = vi.fn();
|
const onPrint = vi.fn();
|
hiprintMocks.PrintTemplate.mockImplementation(() => ({ on, printByHtml2 }));
|
|
await onlineUtils.hiprint({
|
clientOptions: { title: 'HTML 报表' },
|
html: '<!doctype html><html><head><style>.report { color: red; }</style></head><body><main class="report">报表</main></body></html>',
|
onPrint,
|
});
|
|
expect(printByHtml2).toHaveBeenCalledWith(expect.stringContaining('class="jnpf-hiprint-html"'), {
|
printer: '',
|
styleHandler: undefined,
|
title: 'HTML 报表',
|
});
|
const printedHtml = printByHtml2.mock.calls[0]?.[0] || '';
|
expect(printedHtml).toContain('.report { color: red; }');
|
expect(printedHtml).toContain('<main class="report">报表</main>');
|
expect(printedHtml).not.toContain('<html');
|
expect(onPrint).not.toHaveBeenCalled();
|
|
on.mock.calls.find(([eventName]) => eventName === 'printSuccess')?.[1]();
|
|
expect(onPrint).toHaveBeenCalledWith({ mode: 'hiprint' });
|
});
|
|
it('forces HTML file template print pages to be visible in the Hiprint client', async () => {
|
hiprintMocks.socket.opened = true;
|
const printByHtml2 = vi.fn();
|
hiprintMocks.PrintTemplate.mockImplementation(() => ({ on: vi.fn(), printByHtml2 }));
|
|
await onlineUtils.hiprint({
|
html: '<html><head><style>@media screen { .print-pages { display: none !important; } }</style></head><body><div class="screen-view">预览</div><div class="print-pages">打印内容</div></body></html>',
|
htmlPrintMode: 'template',
|
});
|
|
const printedHtml = printByHtml2.mock.calls[0]?.[0] || '';
|
expect(printedHtml).toContain('.jnpf-hiprint-html .screen-view { display: none !important; }');
|
expect(printedHtml).toContain('.jnpf-hiprint-html .print-pages { display: block !important; }');
|
expect(printedHtml).toContain('打印内容');
|
});
|
|
it('reports an error without browser printing when the Hiprint client is unavailable', async () => {
|
const print = vi.fn();
|
const print2 = vi.fn();
|
const onPrint = vi.fn();
|
const onError = vi.fn();
|
const styleHandler = vi.fn(() => '<style></style>');
|
|
await onlineUtils.hiprint({
|
data: [{ id: 'report-1' }],
|
onError,
|
onPrint,
|
styleHandler,
|
template: { print, print2 },
|
timeout: 0,
|
});
|
|
expect(hiprintMocks.autoConnect).toHaveBeenCalledTimes(1);
|
expect(print).not.toHaveBeenCalled();
|
expect(print2).not.toHaveBeenCalled();
|
expect(onPrint).not.toHaveBeenCalled();
|
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'Print client is unavailable' }));
|
expect(messageMocks.error).toHaveBeenCalledWith({ content: '打印客户端未连接,请启动客户端后重试', duration: 3, type: 'error' });
|
});
|
|
it('reports an error without browser printing when direct printing fails', async () => {
|
hiprintMocks.socket.opened = true;
|
const error = new Error('printer unavailable');
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const print = vi.fn();
|
const print2 = vi.fn(() => {
|
throw error;
|
});
|
const onError = vi.fn();
|
|
await onlineUtils.hiprint({
|
data: { id: 'report-1' },
|
onError,
|
template: { print, print2 },
|
});
|
|
expect(print).not.toHaveBeenCalled();
|
expect(onError).toHaveBeenCalledWith(error);
|
expect(messageMocks.error).toHaveBeenCalledWith({ content: '打印失败,请检查打印客户端和打印机后重试', duration: 3, type: 'error' });
|
errorSpy.mockRestore();
|
});
|
});
|
|
describe('onlineUtils.recordDmsPrintOperation', () => {
|
beforeEach(() => {
|
vi.clearAllMocks();
|
requestMocks.post.mockResolvedValue({ code: 200 });
|
});
|
|
it('records only the explicitly supplied DMS print operation', () => {
|
const clientEventId = onlineUtils.recordDmsPrintOperation({
|
copies: 2,
|
printName: ' 受控文件打印 ',
|
printType: 'CONTROLLED_DOCUMENT',
|
remark: '车间使用',
|
sourceType: 'FILE_PREVIEW',
|
targets: [{ documentId: 'document-1', targetId: 'document-1', targetType: 'DOCUMENT' }],
|
});
|
|
expect(clientEventId).toMatch(/^[0-9a-f-]{36}$/);
|
expect(requestMocks.post).toHaveBeenCalledTimes(1);
|
expect(requestMocks.post).toHaveBeenCalledWith(
|
{
|
data: {
|
clientEventId,
|
copies: 2,
|
printName: '受控文件打印',
|
printType: 'CONTROLLED_DOCUMENT',
|
remark: '车间使用',
|
sourceType: 'FILE_PREVIEW',
|
targets: [{ documentId: 'document-1', targetId: 'document-1', targetType: 'DOCUMENT' }],
|
},
|
url: '/api/dms/print-operations',
|
},
|
{ errorMessageMode: 'none' },
|
);
|
});
|
|
it('uses one copy by default and retains a caller event id for retries', () => {
|
const clientEventId = '123e4567-e89b-12d3-a456-426614174000';
|
|
expect(
|
onlineUtils.recordDmsPrintOperation({
|
clientEventId,
|
printName: '记录打印',
|
printType: 'RECORD_DOCUMENT',
|
sourceType: 'FORM_DETAIL',
|
targets: [{ documentCode: 'BG-001', documentName: '报告', targetId: 'record-1', targetType: 'BUSINESS_RECORD' }],
|
}),
|
).toBe(clientEventId);
|
expect(requestMocks.post.mock.calls[0]?.[0]?.data.copies).toBe(1);
|
});
|
|
it('does not submit invalid required data', () => {
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
expect(
|
onlineUtils.recordDmsPrintOperation({
|
printName: ' ',
|
printType: 'OTHER',
|
sourceType: 'FORM_DETAIL',
|
targets: [{ targetId: '1', targetType: 'BUSINESS_RECORD' }],
|
}),
|
).toBeUndefined();
|
expect(onlineUtils.recordDmsPrintOperation({ printName: '打印', printType: 'OTHER', sourceType: 'FORM_DETAIL', targets: [] })).toBeUndefined();
|
expect(requestMocks.post).not.toHaveBeenCalled();
|
|
errorSpy.mockRestore();
|
});
|
|
it('swallows asynchronous recording failures', async () => {
|
const failure = new Error('DMS unavailable');
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
requestMocks.post.mockRejectedValue(failure);
|
|
expect(() =>
|
onlineUtils.recordDmsPrintOperation({
|
printName: '记录打印',
|
printType: 'RECORD_DOCUMENT',
|
sourceType: 'FORM_DETAIL',
|
targets: [{ targetId: 'record-1', targetType: 'BUSINESS_RECORD' }],
|
}),
|
).not.toThrow();
|
await Promise.resolve();
|
|
expect(errorSpy).toHaveBeenCalledWith('[onlineUtils.recordDmsPrintOperation] record failed', failure);
|
errorSpy.mockRestore();
|
});
|
});
|
|
describe('buildPrintAuditTargets', () => {
|
it('uses standard ids without guessing business fields', () => {
|
expect(
|
buildPrintAuditTargets([
|
{ f_id: 'legacy-1', randomCode: 'SHOULD-NOT-BE-USED' },
|
{ id: 'record-2', title: 'SHOULD-NOT-BE-USED' },
|
]),
|
).toEqual([{ id: 'legacy-1' }, { id: 'record-2' }]);
|
});
|
|
it('uses explicit nested mappings and skips rows without an id', () => {
|
expect(
|
buildPrintAuditTargets([{ report: { code: 'BG-001', id: 'record-1', name: '第一份' } }, { report: { code: 'BG-002' } }], {
|
bizCodeField: 'report.code',
|
idField: 'report.id',
|
titleField: 'report.name',
|
}),
|
).toEqual([{ bizCode: 'BG-001', id: 'record-1', title: '第一份' }]);
|
});
|
});
|