Skip to content

Commit 0a99301

Browse files
[PRME-193] Fix script execution when opening pdf's in new tab (#754)
1 parent f9941b0 commit 0a99301

File tree

6 files changed

+108
-101
lines changed

6 files changed

+108
-101
lines changed

app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.test.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -424,24 +424,6 @@ describe('DocumentSelectOrderStage', () => {
424424
});
425425
});
426426

427-
describe('File Preview', () => {
428-
it('creates object URL for file preview', () => {
429-
renderSut(documents);
430-
431-
const viewLink = screen.getByTestId('document-preview-1');
432-
expect(viewLink).toHaveAttribute('href', 'mocked-url');
433-
expect(global.URL.createObjectURL).toHaveBeenCalledWith(documents[0].file);
434-
});
435-
436-
it('opens file preview in new tab', () => {
437-
renderSut(documents);
438-
439-
const viewLink = screen.getByTestId('document-preview-1');
440-
expect(viewLink).toHaveAttribute('target', '_blank');
441-
expect(viewLink).toHaveAttribute('rel', 'noreferrer');
442-
});
443-
});
444-
445427
describe('PDF Viewer Integration', () => {
446428
it('renders PDF viewer when Lloyd George preview is shown', async () => {
447429
renderSut(documents);

app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button, Select, Table } from 'nhsuk-react-components';
2-
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
2+
import { Dispatch, JSX, SetStateAction, useEffect, useRef } from 'react';
33
import { FieldErrors, FieldValues, useForm } from 'react-hook-form';
4-
import { useNavigate } from 'react-router';
4+
import { Link, useNavigate } from 'react-router';
55
import useTitle from '../../../../helpers/hooks/useTitle';
66
import {
77
fileUploadErrorMessages,
@@ -19,6 +19,7 @@ import BackButton from '../../../generic/backButton/BackButton';
1919
import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary';
2020
import ErrorBox from '../../../layout/errorBox/ErrorBox';
2121
import DocumentUploadLloydGeorgePreview from '../documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview';
22+
import getMergedPdfBlob from '../../../../helpers/utils/pdfMerger';
2223

2324
type Props = {
2425
documents: UploadDocument[];
@@ -29,7 +30,7 @@ type FormData = {
2930
[key: string]: number | null;
3031
};
3132

32-
const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }: Props) => {
33+
const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }: Props): JSX.Element => {
3334
const navigate = useNavigate();
3435

3536
const documentPositionKey = (documentId: string): string => {
@@ -132,7 +133,7 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
132133
);
133134
};
134135

135-
const onRemove = (index: number) => {
136+
const onRemove = (index: number): void => {
136137
let updatedDocList: UploadDocument[] = [...documents];
137138
const docToRemove = documents[index];
138139
const key = documentPositionKey(documents[index].id);
@@ -153,7 +154,7 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
153154
setDocuments(updatedDocList);
154155
};
155156

156-
const updateDocumentPositions = () => {
157+
const updateDocumentPositions = (): void => {
157158
const fieldValues = getValues();
158159

159160
const updatedDocuments = documents.map((doc) => ({
@@ -164,7 +165,7 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
164165
setDocuments(updatedDocuments);
165166
};
166167

167-
const submitDocuments = () => {
168+
const submitDocuments = (): void => {
168169
updateDocumentPositions();
169170
if (documents.length === 1) {
170171
navigate(routeChildren.DOCUMENT_UPLOAD_UPLOADING);
@@ -173,7 +174,7 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
173174
navigate(routeChildren.DOCUMENT_UPLOAD_CONFIRMATION);
174175
};
175176

176-
const handleErrors = (_: FieldValues) => {
177+
const handleErrors = (_: FieldValues): void => {
177178
scrollToRef.current?.scrollIntoView();
178179
};
179180

@@ -192,6 +193,13 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
192193
})
193194
.filter((item) => item !== undefined);
194195

196+
const viewPdfFile = async (file: File): Promise<void> => {
197+
const blob = await getMergedPdfBlob([file]);
198+
const url = URL.createObjectURL(blob);
199+
200+
window.open(url);
201+
};
202+
195203
return (
196204
<>
197205
<BackButton />
@@ -285,15 +293,14 @@ const DocumentSelectOrderStage = ({ documents, setDocuments, setMergedPdfBlob }:
285293
)}
286294
</Table.Cell>
287295
<Table.Cell>
288-
<a
289-
href={URL.createObjectURL(document.file)}
296+
<Link
297+
to=""
298+
onClick={() => viewPdfFile(document.file)}
290299
aria-label="Preview - opens in a new tab"
291300
data-testid={`document-preview-${document.id}`}
292-
target="_blank"
293-
rel="noreferrer"
294301
>
295302
View
296-
</a>
303+
</Link>
297304
</Table.Cell>
298305
<Table.Cell>
299306
<button

app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.test.tsx

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,17 @@ import {
55
DOCUMENT_UPLOAD_STATE,
66
} from '../../../../types/pages/UploadDocumentsPage/types';
77
import DocumentUploadLloydGeorgePreview from './DocumentUploadLloydGeorgePreview';
8+
import getMergedPdfBlob from '../../../../helpers/utils/pdfMerger';
89

910
const mockNavigate = vi.fn();
11+
1012
vi.mock('../../../../helpers/hooks/usePatient');
13+
vi.mock('../../../../helpers/utils/pdfMerger');
1114
vi.mock('react-router-dom', () => ({
1215
useNavigate: () => mockNavigate,
1316
}));
1417

15-
// Mock PDF merger
16-
const mockAdd = vi.fn();
17-
const mockSetMetadata = vi.fn();
18-
const mockSaveAsBlob = vi.fn();
19-
20-
vi.mock('pdf-merger-js/browser', () => ({
21-
default: vi.fn(() => ({
22-
add: mockAdd,
23-
setMetadata: mockSetMetadata,
24-
saveAsBlob: mockSaveAsBlob,
25-
})),
26-
}));
27-
28-
URL.createObjectURL = vi.fn();
18+
URL.createObjectURL = () => 'https://example.com';
2919

3020
const createMockDocument = (id: string): UploadDocument => ({
3121
state: DOCUMENT_UPLOAD_STATE.SELECTED,
@@ -42,14 +32,6 @@ describe('DocumentUploadCompleteStage', () => {
4232
beforeEach(() => {
4333
import.meta.env.VITE_ENVIRONMENT = 'vitest';
4434
documents = [];
45-
46-
// Reset mocks
47-
mockAdd.mockClear().mockResolvedValue(undefined);
48-
mockSetMetadata.mockClear().mockResolvedValue(undefined);
49-
mockSaveAsBlob
50-
.mockClear()
51-
.mockResolvedValue(new Blob(['test'], { type: 'application/pdf' }));
52-
URL.createObjectURL = vi.fn().mockReturnValue('blob:test-url');
5335
});
5436
afterEach(() => {
5537
vi.clearAllMocks();
@@ -68,26 +50,10 @@ describe('DocumentUploadCompleteStage', () => {
6850
expect(screen.queryByTestId('pdf-viewer')).not.toBeInTheDocument();
6951
});
7052

71-
it('renders PdfViewer when documents are provided and merged', async () => {
72-
const testDocuments = [createMockDocument('1'), createMockDocument('2')];
73-
74-
render(
75-
<DocumentUploadLloydGeorgePreview
76-
documents={testDocuments}
77-
setMergedPdfBlob={mockSetMergedPdfBlob}
78-
/>,
79-
);
80-
81-
// Wait for the PDF merger to complete and the PdfViewer to render
82-
await waitFor(() => {
83-
expect(screen.getByTestId('pdf-viewer')).toBeInTheDocument();
84-
});
85-
});
86-
87-
it('calls setMergedPdfBlob with the merged PDF blob', async () => {
53+
it('renders pdf viewer and calls setMergedPdfBlob with the merged PDF blob when it has docs', async () => {
8854
const testDocuments = [createMockDocument('1')];
8955
const mockBlob = new Blob(['test pdf content'], { type: 'application/pdf' });
90-
mockSaveAsBlob.mockResolvedValue(mockBlob);
56+
vi.mocked(getMergedPdfBlob).mockResolvedValue(mockBlob);
9157

9258
render(
9359
<DocumentUploadLloydGeorgePreview
@@ -99,6 +65,7 @@ describe('DocumentUploadCompleteStage', () => {
9965
// Wait for the PDF merger to complete
10066
await waitFor(() => {
10167
expect(mockSetMergedPdfBlob).toHaveBeenCalledWith(mockBlob);
68+
expect(screen.getByTestId('pdf-viewer')).toBeInTheDocument();
10269
});
10370
});
10471
});

app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.tsx

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import PDFMerger from 'pdf-merger-js/browser';
21
import { UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
3-
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
2+
import { Dispatch, JSX, SetStateAction, useEffect, useRef, useState } from 'react';
43
import PdfViewer from '../../../generic/pdfViewer/PdfViewer';
4+
import getMergedPdfBlob from '../../../../helpers/utils/pdfMerger';
55

66
type Props = {
77
documents: UploadDocument[];
88
setMergedPdfBlob: Dispatch<SetStateAction<Blob | undefined>>;
99
};
1010

11-
const DocumentUploadLloydGeorgePreview = ({ documents, setMergedPdfBlob }: Props) => {
11+
const DocumentUploadLloydGeorgePreview = ({ documents, setMergedPdfBlob }: Props): JSX.Element => {
1212
const [mergedPdfUrl, setMergedPdfUrl] = useState('');
1313

1414
const runningRef = useRef(false);
@@ -19,32 +19,9 @@ const DocumentUploadLloydGeorgePreview = ({ documents, setMergedPdfBlob }: Props
1919

2020
runningRef.current = true;
2121

22-
const render = async () => {
23-
const merger = new PDFMerger();
22+
const render = async (): Promise<void> => {
23+
const blob = await getMergedPdfBlob(documents.map((doc) => doc.file));
2424

25-
for (const doc of documents) {
26-
let attempts = 0;
27-
28-
do {
29-
try {
30-
await merger.add(doc.file);
31-
32-
attempts = 3;
33-
} catch (err) {
34-
attempts += 1;
35-
36-
if (attempts === 3) {
37-
throw err;
38-
}
39-
}
40-
} while (attempts < 3);
41-
}
42-
43-
await merger.setMetadata({
44-
producer: 'pdf-merger-js based script',
45-
});
46-
47-
const blob = await merger.saveAsBlob();
4825
setMergedPdfBlob(blob);
4926

5027
const url = URL.createObjectURL(blob);
@@ -59,15 +36,12 @@ const DocumentUploadLloydGeorgePreview = ({ documents, setMergedPdfBlob }: Props
5936
});
6037
}, [JSON.stringify(documents)]);
6138

62-
const loaded = () => {};
63-
6439
return (
6540
<>
6641
{documents && mergedPdfUrl && (
6742
<PdfViewer
6843
customClasses={['upload-preview']}
6944
fileUrl={mergedPdfUrl}
70-
onLoaded={loaded}
7145
/>
7246
)}
7347
</>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import getMergedPdfBlob from './pdfMerger';
2+
3+
// Mock PDF merger
4+
const mockAdd = vi.fn();
5+
const mockSetMetadata = vi.fn();
6+
const mockSaveAsBlob = vi.fn();
7+
8+
vi.mock('pdf-merger-js/browser', () => ({
9+
default: vi.fn(() => ({
10+
add: mockAdd,
11+
setMetadata: mockSetMetadata,
12+
saveAsBlob: mockSaveAsBlob,
13+
})),
14+
}));
15+
16+
describe('getMergedPdfBlob', () => {
17+
beforeEach(() => {
18+
import.meta.env.VITE_ENVIRONMENT = 'vitest';
19+
20+
// Reset mocks
21+
mockAdd.mockClear().mockResolvedValue(undefined);
22+
mockSetMetadata.mockClear().mockResolvedValue(undefined);
23+
mockSaveAsBlob
24+
.mockClear()
25+
.mockResolvedValue(new Blob(['test'], { type: 'application/pdf' }));
26+
URL.createObjectURL = vi.fn().mockReturnValue('blob:test-url');
27+
});
28+
afterEach(() => {
29+
vi.clearAllMocks();
30+
});
31+
32+
it('should return expected blob', async () => {
33+
const file = new File(['test'], 'test.pdf', { type: 'application/pdf' });
34+
35+
const mockBlob = new Blob(['test pdf content'], { type: 'application/pdf' });
36+
mockSaveAsBlob.mockResolvedValue(mockBlob);
37+
38+
const blob = await getMergedPdfBlob([file]);
39+
40+
expect(blob).toBe(mockBlob);
41+
});
42+
});

app/src/helpers/utils/pdfMerger.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import PDFMerger from 'pdf-merger-js/browser';
2+
3+
const getMergedPdfBlob = async (pdfFiles: File[]) => {
4+
if (pdfFiles.length < 1) {
5+
throw Error('Cannot merge empty pdf array');
6+
}
7+
8+
const merger = new PDFMerger();
9+
10+
for (const pdf of pdfFiles) {
11+
let attempts = 0;
12+
13+
do {
14+
try {
15+
await merger.add(pdf);
16+
17+
attempts = 3;
18+
} catch (err) {
19+
attempts += 1;
20+
21+
if (attempts === 3) {
22+
throw err;
23+
}
24+
}
25+
} while (attempts < 3);
26+
}
27+
28+
await merger.setMetadata({
29+
producer: 'National Document Respository',
30+
});
31+
32+
return await merger.saveAsBlob();
33+
};
34+
35+
export default getMergedPdfBlob;

0 commit comments

Comments
 (0)