Skip to content

Commit 2655aa0

Browse files
[PRMP-550] Add error handling for duplicate file names and wrong file types (#824)
1 parent ac1040c commit 2655aa0

File tree

5 files changed

+111
-64
lines changed

5 files changed

+111
-64
lines changed

app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.test.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
DOCUMENT_UPLOAD_STATE,
1010
DOCUMENT_TYPE,
1111
} from '../../../../types/pages/UploadDocumentsPage/types';
12-
import { UPLOAD_FILE_ERROR_TYPE } from '../../../../helpers/utils/fileUploadErrorMessages';
12+
import {
13+
fileUploadErrorMessages,
14+
UPLOAD_FILE_ERROR_TYPE,
15+
} from '../../../../helpers/utils/fileUploadErrorMessages';
1316
import { routes } from '../../../../types/generic/routes';
1417

1518
const createFailedDocument = (name: string, error: UPLOAD_FILE_ERROR_TYPE): UploadDocument => ({
@@ -77,25 +80,21 @@ describe('DocumentSelectFileErrorsPage', () => {
7780
});
7881

7982
it.each([
80-
{
81-
error: UPLOAD_FILE_ERROR_TYPE.invalidPdf,
82-
expectedMessage: 'This file is damaged or unreadable',
83-
},
84-
{
85-
error: UPLOAD_FILE_ERROR_TYPE.emptyPdf,
86-
expectedMessage: 'This file is empty',
87-
},
88-
{
89-
error: UPLOAD_FILE_ERROR_TYPE.passwordProtected,
90-
expectedMessage: 'This file is password protected',
91-
},
92-
])('displays correct error message for "$error" file', ({ error, expectedMessage }) => {
83+
UPLOAD_FILE_ERROR_TYPE.invalidPdf,
84+
UPLOAD_FILE_ERROR_TYPE.emptyPdf,
85+
UPLOAD_FILE_ERROR_TYPE.passwordProtected,
86+
UPLOAD_FILE_ERROR_TYPE.duplicateFileName,
87+
UPLOAD_FILE_ERROR_TYPE.passwordProtected,
88+
UPLOAD_FILE_ERROR_TYPE.invalidFileType,
89+
])('displays correct error message for "$error" file', (error) => {
9390
const fileName = `file-${error}.pdf`;
9491
const doc = createFailedDocument(fileName, error);
9592
renderDocs([doc]);
9693

9794
expect(screen.getByText(fileName)).toBeInTheDocument();
98-
expect(screen.getByText(expectedMessage)).toBeInTheDocument();
95+
expect(
96+
screen.getByText(fileUploadErrorMessages[error].selectFileError!),
97+
).toBeInTheDocument();
9998
});
10099

101100
it('renders multiple error files correctly', () => {

app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.tsx

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,6 @@ type Props = {
1818
const DocumentSelectFileErrorsPage = ({ documents }: Props): JSX.Element => {
1919
const navigate = useNavigate();
2020

21-
const fileErrorText = (errorType: UPLOAD_FILE_ERROR_TYPE): string | undefined => {
22-
switch (errorType) {
23-
case UPLOAD_FILE_ERROR_TYPE.invalidPdf:
24-
return fileUploadErrorMessages.invalidPdf.selectFileError;
25-
case UPLOAD_FILE_ERROR_TYPE.emptyPdf:
26-
return fileUploadErrorMessages.emptyPdf.selectFileError;
27-
case UPLOAD_FILE_ERROR_TYPE.passwordProtected:
28-
return fileUploadErrorMessages.passwordProtected.selectFileError;
29-
}
30-
};
31-
3221
useEffect(() => {
3322
if (documents.length === 0) {
3423
navigate(routes.HOME);
@@ -47,7 +36,7 @@ const DocumentSelectFileErrorsPage = ({ documents }: Props): JSX.Element => {
4736
{documents.map((doc) => (
4837
<div key={doc.id}>
4938
<ErrorMessage className="mb-1">{doc.file.name}</ErrorMessage>
50-
<p>{fileErrorText(doc.error!)}</p>
39+
<p>{fileUploadErrorMessages[doc.error!]?.selectFileError}</p>
5140
</div>
5241
))}
5342

app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.test.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { JSX, useRef, useState } from 'react';
88
import * as ReactRouter from 'react-router-dom';
99
import usePatient from '../../../../helpers/hooks/usePatient';
1010
import { buildLgFile, buildPatientDetails } from '../../../../helpers/test/testBuilders';
11-
import { PDF_PARSING_ERROR_TYPE } from '../../../../helpers/utils/fileUploadErrorMessages';
11+
import { PDF_PARSING_ERROR_TYPE, UPLOAD_FILE_ERROR_TYPE } from '../../../../helpers/utils/fileUploadErrorMessages';
1212
import { getFormattedDate } from '../../../../helpers/utils/formatDate';
1313
import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
1414
import { routeChildren, routes } from '../../../../types/generic/routes';
@@ -208,6 +208,43 @@ describe('DocumentSelectStage', () => {
208208
});
209209
},
210210
);
211+
212+
it('should navigate to file errors page when user selects a file that is not a PDF', async () => {
213+
renderApp(history);
214+
const dropzone = screen.getByTestId('dropzone');
215+
const nonPdfFile = {...lgDocumentOne, type: 'text/plain' };
216+
fireEvent.drop(dropzone, {
217+
dataTransfer: { files: [nonPdfFile] },
218+
});
219+
220+
await waitFor(() => {
221+
expect(mockedUseNavigate).toHaveBeenCalledWith(
222+
routeChildren.DOCUMENT_UPLOAD_FILE_ERRORS,
223+
);
224+
});
225+
});
226+
227+
it('should navigate to file errors page when user selects a file that is a duplicate file name', async () => {
228+
renderApp(history);
229+
const dropzone = screen.getByTestId('dropzone');
230+
fireEvent.drop(dropzone, {
231+
dataTransfer: { files: [lgDocumentOne] },
232+
});
233+
234+
await waitFor(() => {
235+
expect(screen.getByText(lgDocumentOne.name)).toBeInTheDocument();
236+
});
237+
238+
fireEvent.drop(dropzone, {
239+
dataTransfer: { files: [lgDocumentOne] },
240+
});
241+
242+
await waitFor(() => {
243+
expect(mockedUseNavigate).toHaveBeenCalledWith(
244+
routeChildren.DOCUMENT_UPLOAD_FILE_ERRORS,
245+
);
246+
});
247+
});
211248
});
212249

213250
const TestApp = (props: Partial<Props>): JSX.Element => {

app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.tsx

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -84,54 +84,64 @@ const DocumentSelectStage = ({
8484
}
8585

8686
if (fileArray) {
87-
void updateFileList(fileArray.filter(validateFileType));
87+
void updateFileList(fileArray);
8888
}
8989
};
9090

9191
const onInput = (e: FileInputEvent): void => {
9292
fileInputAreaRef.current?.scrollIntoView({ behavior: 'smooth' });
93-
const fileArray = Array.from(e.target.files ?? new FileList()).filter(validateFileType);
93+
const fileArray = Array.from(e.target.files ?? new FileList());
9494

9595
void updateFileList(fileArray);
9696
};
9797

9898
const updateFileList = async (fileArray: File[]): Promise<void> => {
99-
const documentMap = fileArray
100-
.filter((f) => !documents.some((d) => d.file.name === f.name))
101-
.map(async (file) => {
102-
const document: UploadDocument = {
103-
id: uuidv4(),
104-
file,
105-
state: DOCUMENT_UPLOAD_STATE.SELECTED,
106-
progress: 0,
107-
docType: documentType,
108-
attempts: 0,
109-
numPages: 0,
110-
validated: false,
111-
};
112-
113-
const buffer = await file.arrayBuffer();
114-
115-
try {
116-
const pdf = await getDocument(buffer).promise;
117-
await pdf.getPage(1);
118-
document.numPages = pdf.numPages;
119-
await pdf.destroy();
120-
} catch (e) {
121-
const error = e as Error;
122-
document.state = DOCUMENT_UPLOAD_STATE.FAILED;
123-
124-
if (error.message === PDF_PARSING_ERROR_TYPE.INVALID_PDF_STRUCTURE) {
125-
document.error = UPLOAD_FILE_ERROR_TYPE.invalidPdf;
126-
} else if (error.message === PDF_PARSING_ERROR_TYPE.PASSWORD_MISSING) {
127-
document.error = UPLOAD_FILE_ERROR_TYPE.passwordProtected;
128-
} else if (error.message === PDF_PARSING_ERROR_TYPE.EMPTY_PDF) {
129-
document.error = UPLOAD_FILE_ERROR_TYPE.emptyPdf;
130-
}
131-
}
99+
const documentMap = fileArray.map(async (file) => {
100+
const document: UploadDocument = {
101+
id: uuidv4(),
102+
file,
103+
state: DOCUMENT_UPLOAD_STATE.SELECTED,
104+
progress: 0,
105+
docType: documentType,
106+
attempts: 0,
107+
numPages: 0,
108+
validated: false,
109+
};
110+
111+
if (!validateFileType(file)) {
112+
document.state = DOCUMENT_UPLOAD_STATE.FAILED;
113+
document.error = UPLOAD_FILE_ERROR_TYPE.invalidFileType;
114+
return document;
115+
}
132116

117+
if (documents.some((d) => d.file.name === document.file.name)) {
118+
document.state = DOCUMENT_UPLOAD_STATE.FAILED;
119+
document.error = UPLOAD_FILE_ERROR_TYPE.duplicateFileName;
133120
return document;
134-
});
121+
}
122+
123+
const buffer = await file.arrayBuffer();
124+
125+
try {
126+
const pdf = await getDocument(buffer).promise;
127+
await pdf.getPage(1);
128+
document.numPages = pdf.numPages;
129+
await pdf.destroy();
130+
} catch (e) {
131+
const error = e as Error;
132+
document.state = DOCUMENT_UPLOAD_STATE.FAILED;
133+
134+
if (error.message === PDF_PARSING_ERROR_TYPE.INVALID_PDF_STRUCTURE) {
135+
document.error = UPLOAD_FILE_ERROR_TYPE.invalidPdf;
136+
} else if (error.message === PDF_PARSING_ERROR_TYPE.PASSWORD_MISSING) {
137+
document.error = UPLOAD_FILE_ERROR_TYPE.passwordProtected;
138+
} else if (error.message === PDF_PARSING_ERROR_TYPE.EMPTY_PDF) {
139+
document.error = UPLOAD_FILE_ERROR_TYPE.emptyPdf;
140+
}
141+
}
142+
143+
return document;
144+
});
135145

136146
const docs = await Promise.all(documentMap);
137147

app/src/helpers/utils/fileUploadErrorMessages.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export enum UPLOAD_FILE_ERROR_TYPE {
99
invalidPdf = 'invalidPdf',
1010
emptyPdf = 'emptyPdf',
1111
duplicatePositionError = 'duplicatePositionError',
12+
duplicateFileName = 'duplicateFileName',
13+
invalidFileType = 'invalidFileType',
1214
}
1315

1416
export enum PDF_PARSING_ERROR_TYPE {
@@ -57,4 +59,14 @@ export const fileUploadErrorMessages: ErrorMessageType = {
5759
inline: 'You have selected the same position number for two or more files',
5860
errorBox: 'You have selected the same position number for two or more files',
5961
},
62+
duplicateFileName: {
63+
inline: 'This file has the same name as another file you have selected',
64+
errorBox: 'This file has the same name as another file you have selected',
65+
selectFileError: 'This file has the same name as another file you have selected',
66+
},
67+
invalidFileType: {
68+
inline: 'This file is not in the correct file format',
69+
errorBox: 'This file is not in the correct file format',
70+
selectFileError: 'This file is not in the correct file format',
71+
},
6072
};

0 commit comments

Comments
 (0)