Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,44 +1,101 @@
import { render, waitFor, screen } from '@testing-library/react';
import DocumentUploadConfirmStage from './DocumentUploadConfirmStage';
import { UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
import { getFormattedDate } from '../../../../helpers/utils/formatDate';
import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
import usePatient from '../../../../helpers/hooks/usePatient';
import {
DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
import * as ReactRouter from 'react-router-dom';
import { MemoryHistory, createMemoryHistory } from 'history';
import userEvent from '@testing-library/user-event';
import { routes } from '../../../../types/generic/routes';

const mockNavigate = vi.fn();
const mockedUseNavigate = vi.fn();
vi.mock('../../../../helpers/hooks/usePatient');
vi.mock('react-router-dom', () => ({
useNavigate: () => mockNavigate,
}));
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
return {
...actual,
useNavigate: () => mockedUseNavigate,
};
});

const patientDetails = buildPatientDetails();

URL.createObjectURL = vi.fn();

let history = createMemoryHistory({
initialEntries: ['/'],
initialIndex: 0,
});

describe('DocumentUploadCompleteStage', () => {
let documents: UploadDocument[];
const mockStartUpload = vi.fn();

beforeEach(() => {
vi.mocked(usePatient).mockReturnValue(patientDetails);

import.meta.env.VITE_ENVIRONMENT = 'vitest';
documents = [];
history = createMemoryHistory({ initialEntries: ['/'], initialIndex: 0 });
});
afterEach(() => {
vi.clearAllMocks();
});

describe('Rendering', () => {
it('renders', async () => {
renderSut(documents);
it('renders', async () => {
renderApp(history, 1);

await waitFor(async () => {
expect(screen.getByText('Check your files before uploading')).toBeInTheDocument();
});
});

it('should trigger start upload when confirm button is clicked', async () => {
renderApp(history, 1);

userEvent.click(await screen.findByTestId('confirm-button'));

await waitFor(async () => {
expect(screen.getByText('Check your files before uploading')).toBeInTheDocument();
await waitFor(() => {
expect(mockStartUpload).toHaveBeenCalled();
});
});

it('should render pagination when doc count is high enough', async () => {
renderApp(history, 15);

await waitFor(async () => {
expect(await screen.findByTestId('page-1-button')).toBeInTheDocument();
expect(await screen.findByTestId('page-2-button')).toBeInTheDocument();
});
});

describe('Navigation', () => {
it('should navigate to previous screen when go back is clicked', async () => {
renderApp(history, 1);

userEvent.click(await screen.findByTestId('go-back-link'));

await waitFor(() => {
expect(mockedUseNavigate).toHaveBeenCalledWith(-1);
});
});

it('should navigate to the file selection page when change files is clicked', async () => {
renderApp(history, 1);

userEvent.click(await screen.findByTestId('change-files-button'));

await waitFor(() => {
expect(mockedUseNavigate).toHaveBeenCalledWith(routes.DOCUMENT_UPLOAD);
});
});

it('renders patient summary fields is inset', async () => {
renderSut(documents);
renderApp(history, 1);

const insetText = screen
.getByText('Make sure that all files uploaded are for this patient only:')
Expand All @@ -58,10 +115,23 @@ describe('DocumentUploadCompleteStage', () => {
expect(screen.getByText(expectedDob)).toBeInTheDocument();
});
});
});

function renderSut(documents: UploadDocument[]) {
render(
<DocumentUploadConfirmStage documents={documents} startUpload={() => Promise.resolve()} />,
);
}
const renderApp = (history: MemoryHistory, docsLength: number) => {
const documents: UploadDocument[] = [];
for (let i = 1; i <= docsLength; i++) {
documents.push({
attempts: 0,
id: `${i}`,
docType: DOCUMENT_TYPE.LLOYD_GEORGE,
file: new File(['file'], `file ${i}.pdf`),
state: DOCUMENT_UPLOAD_STATE.SELECTED,
});
}

return render(
<ReactRouter.Router navigator={history} location={history.location}>
<DocumentUploadConfirmStage documents={documents} startUpload={mockStartUpload} />
</ReactRouter.Router>,
);
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,9 @@ const DocumentUploadConfirmStage = ({ documents, startUpload }: Props) => {

return (
<div className="document-upload-confirm">
<BackButton />
<BackButton dataTestid="go-back-link" />
<h1>{pageTitle}</h1>
<p>
Files will be combined into a single PDF document to create a digital Lloyd George
record for:
</p>

<div className="nhsuk-inset-text">
<p>Make sure that all files uploaded are for this patient only:</p>
<PatientSummary>
Expand All @@ -46,31 +43,34 @@ const DocumentUploadConfirmStage = ({ documents, startUpload }: Props) => {
<PatientSummary.Child item={PatientInfo.BIRTH_DATE} />
</PatientSummary>
</div>
<div style={{ borderBottom: '1px solid black' }}>
<h4 style={{ width: '100%', display: 'flex', justifyContent: 'space-between' }}>
<span>Files to be uploaded</span>
<button
className="govuk-link"
rel="change"
onClick={(e) => {
e.preventDefault();
navigate(routes.DOCUMENT_UPLOAD);
}}
>
<strong style={{ textDecoration: 'underline', cursor: 'pointer' }}>
Change
</strong>
</button>
</h4>
</div>

<p>
Files will be combined into a single PDF document to create a Lloyd George record
for this patient.
</p>

<h4>Files to be uploaded</h4>

<Table id="selected-documents-table">
<Table.Head>
<Table.Row>
<Table.Cell width="80%">Filename</Table.Cell>
<Table.Cell style={{ whiteSpace: 'pre', wordBreak: 'keep-all' }}>
<Table.Cell>Filename</Table.Cell>
<Table.Cell width="25%" className="word-break-keep-all">
Position
</Table.Cell>
<Table.Cell width="10%" className="word-break-keep-all">
<button
className="govuk-link"
rel="change"
data-testid="change-files-button"
onClick={(e) => {
e.preventDefault();
navigate(routes.DOCUMENT_UPLOAD);
}}
>
Change files
</button>
</Table.Cell>
</Table.Row>
</Table.Head>

Expand All @@ -79,7 +79,9 @@ const DocumentUploadConfirmStage = ({ documents, startUpload }: Props) => {
return (
<Table.Row key={document.id} id={document.file.name}>
<Table.Cell>
<div>{document.file.name}</div>
<div>
<strong>{document.file.name}</strong>
</div>
</Table.Cell>
<Table.Cell>
<div>
Expand All @@ -88,6 +90,7 @@ const DocumentUploadConfirmStage = ({ documents, startUpload }: Props) => {
: 'N/A'}
</div>
</Table.Cell>
<Table.Cell></Table.Cell>
</Table.Row>
);
})}
Expand All @@ -100,7 +103,9 @@ const DocumentUploadConfirmStage = ({ documents, startUpload }: Props) => {
setCurrentPage={setCurrentPage}
/>

<Button onClick={startUpload}>Upload files</Button>
<Button data-testid="confirm-button" onClick={startUpload}>
Confirm file order and upload files
</Button>
</div>
);
};
Expand Down
74 changes: 74 additions & 0 deletions app/src/components/generic/pagination/Pagination.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { act, useState } from 'react';
import Pagination, { Props } from './Pagination';
import { render, screen, waitFor } from '@testing-library/react';
import { it, describe } from 'vitest';
import userEvent from '@testing-library/user-event';

describe('Pagination', () => {
it.each([
{ totalPages: 2, expectedLinkCount: 2 },
{ totalPages: 7, expectedLinkCount: 7 },
{ totalPages: 8, expectedLinkCount: 6 },
{ totalPages: 10, expectedLinkCount: 6 },
])("renders expected links when there are '%s' pages", async (theory) => {
renderApp(theory.totalPages);

expect(await screen.findAllByTestId(/page-[0-9]+-button/)).toHaveLength(
theory.expectedLinkCount,
);

expect(screen.queryAllByTestId('page-separator')).toHaveLength(
theory.totalPages > 7 ? 1 : 0,
);

expect(await screen.findByTestId('next-page-button')).toBeInTheDocument();
});

it('should change current page when clicking next page button', async () => {
renderApp(2);

act(() => {
userEvent.click(screen.getByTestId('next-page-button'));
});

await waitFor(async () => {
expect(await screen.findByTestId('previous-page-button')).toBeInTheDocument();
expect(screen.queryByTestId('next-page-button')).not.toBeInTheDocument();

const buttonWrapper = (await screen.findByTestId('page-2-button')).parentElement;
expect(buttonWrapper).toHaveClass('govuk-pagination__item--current');
});
});

it('should change current page when clicking previous page button', async () => {
renderApp(2, 1);

act(() => {
userEvent.click(screen.getByTestId('previous-page-button'));
});

await waitFor(async () => {
expect(await screen.findByTestId('next-page-button')).toBeInTheDocument();
expect(screen.queryByTestId('previous-page-button')).not.toBeInTheDocument();

const buttonWrapper = (await screen.findByTestId('page-1-button')).parentElement;
expect(buttonWrapper).toHaveClass('govuk-pagination__item--current');
});
});

const TestApp = (props: Partial<Props>) => {
const [currentPage, setCurrentPage] = useState<number>(props.currentPage!);

return (
<Pagination
currentPage={currentPage}
setCurrentPage={setCurrentPage}
totalPages={props.totalPages || 0}
></Pagination>
);
};

const renderApp = (totalPages: number, initialPage: number = 0) => {
return render(<TestApp currentPage={initialPage} totalPages={totalPages}></TestApp>);
};
});
23 changes: 18 additions & 5 deletions app/src/components/generic/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch, SetStateAction, MouseEvent } from 'react';

type Props = {
export type Props = {
totalPages: number;
currentPage: number;
setCurrentPage: Dispatch<SetStateAction<number>>;
Expand All @@ -16,11 +16,21 @@ const Pagination = ({ totalPages, currentPage, setCurrentPage }: Props) => {
setCurrentPage(page);
};

const pageNumber = (page: number | null) => {
const pageNumber = (page: number | null, index: number) => {
if (page === null) {
return <li className="govuk-pagination__item govuk-pagination__item--ellipses">...</li>;
return (
<li
key={`separator-${index}`}
data-testid="page-separator"
className="govuk-pagination__item govuk-pagination__item--ellipses"
>
...
</li>
);
}

const displayPage = `${page + 1}`;

return (
<li
key={page}
Expand All @@ -30,9 +40,10 @@ const Pagination = ({ totalPages, currentPage, setCurrentPage }: Props) => {
<button
className="govuk-link govuk-pagination__link"
onClick={(e) => updateCurrentPage(e, page)}
aria-label={`Page ${page + 1}`}
aria-label={`Page ${displayPage}`}
data-testid={`page-${displayPage}-button`}
>
{page + 1}
{displayPage}
</button>
</li>
);
Expand Down Expand Up @@ -91,6 +102,7 @@ const Pagination = ({ totalPages, currentPage, setCurrentPage }: Props) => {
<div className="govuk-pagination__prev">
<button
className="govuk-link govuk-pagination__link"
data-testid="previous-page-button"
rel="prev"
onClick={(e) => updateCurrentPage(e, currentPage - 1)}
>
Expand All @@ -116,6 +128,7 @@ const Pagination = ({ totalPages, currentPage, setCurrentPage }: Props) => {
<div className="govuk-pagination__next">
<button
className="govuk-link govuk-pagination__link"
data-testid="next-page-button"
rel="next"
onClick={(e) => updateCurrentPage(e, currentPage + 1)}
>
Expand Down