Skip to content
Open
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
Expand Up @@ -73,6 +73,8 @@ const mockDocumentRelationshipApi = {
vi.mock('@/features/documents/hooks/useDocumentRelationshipProvider');
vi.mocked(useDocumentRelationshipProvider).mockReturnValue(mockDocumentRelationshipApi);

const mockFile = new File(['(⌐□_□)'], 'test.png', { type: 'image/png' });

describe('DocumentUploadContainer component', () => {
let viewProps: IDocumentUploadFormProps | undefined;
const View: React.FC<IDocumentUploadFormProps> = props => {
Expand Down Expand Up @@ -175,7 +177,7 @@ describe('DocumentUploadContainer component', () => {
'',
[],
);
formDocument.file = new File(['(⌐□_□)'], 'test.png', { type: 'image/png' });
formDocument.setFile(mockFile);

const batchRequest = new BatchUploadFormModel();
batchRequest.documents.push(formDocument);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mockDocumentTypesResponse } from '@/mocks/documents.mock';
import { mockLookups } from '@/mocks/lookups.mock';
import { ApiGen_Mayan_DocumentTypeMetadataType } from '@/models/api/generated/ApiGen_Mayan_DocumentTypeMetadataType';
import { lookupCodesSlice } from '@/store/slices/lookupCodes';
import { act, fireEvent, render, RenderOptions, screen } from '@/utils/test-utils';
import { act, fireEvent, render, RenderOptions, screen, userEvent, waitForEffects } from '@/utils/test-utils';

import { BatchUploadFormModel, DocumentUploadFormData } from '../models';
import DocumentUploadForm from './DocumentUploadForm';
Expand Down Expand Up @@ -140,6 +140,32 @@ describe('DocumentUploadView component', () => {
expect(onUploadDocument).toHaveBeenCalled();
});

it('should enable/disable document replace when replace button clicked', async () => {
const { getByTestId, queryByTestId, formikRef } = setup({ initialValues });

expect(formikRef.current).not.toBeNull();

// get the upload button
const uploader = getByTestId('upload-input');

// simulate upload event and wait until finish
await act(async () => {
fireEvent.change(uploader, {
target: { files: [file] },
});
});

const enableReplaceBtn = getByTestId('enable-replace-btn-0');
await act(async () => userEvent.click(enableReplaceBtn));
await waitForEffects();
expect(getByTestId(`doc-replacement[0]`)).toBeInTheDocument();

const disableReplaceBtn = getByTestId('cancel-replace-btn-0');
await act(async () => userEvent.click(disableReplaceBtn));
await waitForEffects();
expect(queryByTestId(`doc-replacement[0]`)).not.toBeInTheDocument();
});

it('should display the number of attached files', async () => {
const { getByTestId, formikRef } = setup({ initialValues });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export const DocumentUploadForm: React.FunctionComponent<IDocumentUploadFormProp
documentStatusOptions[0]?.value?.toString(),
initialDocumentType ?? '',
[],
file,
);
formDocument.file = file;

push(formDocument);
}

Expand Down Expand Up @@ -89,6 +90,7 @@ export const DocumentUploadForm: React.FunctionComponent<IDocumentUploadFormProp
multiple
/>
</SectionField>

<StyledScrollable>
{formikProps.values.documents.map((formDocument, index) => (
<SectionField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import truncate from 'lodash/truncate';
import { useEffect, useMemo } from 'react';
import { Col, Row } from 'react-bootstrap';
import { FaCheck, FaTrash } from 'react-icons/fa';
import { TbReplace, TbReplaceOff } from 'react-icons/tb';
import styled, { useTheme } from 'styled-components';

import { StyledRemoveIconButton } from '@/components/common/buttons';
Expand All @@ -15,6 +16,8 @@ import { exists } from '@/utils';
import { withNameSpace } from '@/utils/formUtils';

import { BatchUploadFormModel, DocumentUploadFormData } from '../models';
import DocumentUploadReplaceContainer from './documentUploadReplace/DocumentUploadReplaceContainer';
import DocumentUploadReplaceView from './documentUploadReplace/DocumentUploadReplaceView';

export interface ISelectedDocumentHeaderProps {
// props
Expand All @@ -26,6 +29,9 @@ export interface ISelectedDocumentHeaderProps {
document: DocumentUploadFormData;
documentTypes: ApiGen_Concepts_DocumentType[];
documentStatusOptions: SelectOption[];
replacingFile: boolean;
onConfirmDocumentReplace: (file: File) => void;
toggleReplacingFile: () => void;
// event handlers
onDocumentTypeChange: (changeEvent: React.ChangeEvent<HTMLInputElement>) => void;
onRemove: (index: number) => void;
Expand All @@ -40,6 +46,9 @@ export const SelectedDocumentHeader: React.FunctionComponent<ISelectedDocumentHe
document,
documentTypes,
documentStatusOptions,
replacingFile,
onConfirmDocumentReplace,
toggleReplacingFile,
onDocumentTypeChange,
onRemove,
}) => {
Expand Down Expand Up @@ -98,15 +107,53 @@ export const SelectedDocumentHeader: React.FunctionComponent<ISelectedDocumentHe
<span>File {index + 1}:</span>
<span className="ml-4">{truncate(document.file.name, { length: 50 })}</span>
<FaCheck className="ml-2" size="1.6rem" color={theme.css.uploadFileCheckColor} />
{!replacingFile && (
<TbReplace
className="ml-2"
style={{ cursor: 'pointer' }}
size="1.6rem"
color={theme.css.pimsGrey80}
data-testid={`enable-replace-btn-${index}`}
onClick={e => {
e.preventDefault();
e.stopPropagation();
toggleReplacingFile();
}}
/>
)}
{replacingFile && (
<TbReplaceOff
className="ml-2"
style={{ cursor: 'pointer' }}
size="1.6rem"
color={theme.css.pimsRed80}
data-testid={`cancel-replace-btn-${index}`}
onClick={e => {
e.preventDefault();
e.stopPropagation();
toggleReplacingFile();
}}
/>
)}
</Col>
</Row>

{replacingFile && (
<DocumentUploadReplaceContainer
index={index}
onReplaceDocumentFile={onConfirmDocumentReplace}
onCancelReplaceFile={() => toggleReplacingFile()}
View={DocumentUploadReplaceView}
></DocumentUploadReplaceContainer>
)}

<StyledRow className={clsx('ml-0', className)}>
<Col md="5">
<SectionField label={null} contentWidth={{ xs: 12 }} required>
<Select
className="mb-0"
data-testid={withNameSpace(namespace, 'document-type')}
placeholder={documentTypeOptions.length > 1 ? 'Select Document type' : undefined}
placeholder={documentTypeOptions.length > 1 ? 'Select Document type' : ''}
field={withNameSpace(namespace, 'documentTypeId')}
options={documentTypeOptions}
onChange={onDocumentTypeChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormikErrors, FormikProps, getIn } from 'formik';
import { ChangeEvent, useCallback } from 'react';
import { ChangeEvent, useCallback, useState } from 'react';

import { SelectOption } from '@/components/common/form';
import { Section } from '@/components/common/Section/Section';
Expand Down Expand Up @@ -37,6 +37,8 @@ export const SelectedDocumentRow: React.FunctionComponent<ISelectedDocumentRowPr
onRemove,
}) => {
const { setFieldValue } = formikProps;
const [replacingFile, setReplacingFile] = useState<boolean>(false);

const errors: FormikErrors<DocumentUploadFormData> =
getIn(formikProps.errors, namespace ?? '') || {};

Expand Down Expand Up @@ -67,6 +69,11 @@ export const SelectedDocumentRow: React.FunctionComponent<ISelectedDocumentRowPr
[documentTypes, updateDocumentType],
);

const onConfirmDocumentReplace = (file: File) => {
document.setFile(file);
setReplacingFile(!replacingFile);
};

return (
<Section
header={
Expand All @@ -79,6 +86,9 @@ export const SelectedDocumentRow: React.FunctionComponent<ISelectedDocumentRowPr
documentStatusOptions={documentStatusOptions}
onRemove={onRemove}
onDocumentTypeChange={onDocumentTypeChange}
replacingFile={replacingFile}
toggleReplacingFile={() => setReplacingFile(!replacingFile)}
onConfirmDocumentReplace={onConfirmDocumentReplace}
/>
}
isStyledHeader
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { act, render, RenderOptions } from '@/utils/test-utils';
import DocumentUploadReplaceContainer, {
IDocumentUploadReplaceContainerProps,
} from './DocumentUploadReplaceContainer';
import { IDocumentUploadReplaceViewProps } from './DocumentUploadReplaceView';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
let viewProps: IDocumentUploadReplaceViewProps | undefined;
const TestView: React.FC<IDocumentUploadReplaceViewProps> = props => {
viewProps = props;
return <span>Content Rendered</span>;
};

const onReplaceDocumentFile = vi.fn();
const onCancelReplaceFile = vi.fn();

describe('document upload replace container component', () => {
const setup = async (
renderOptions: RenderOptions & {
props?: Partial<IDocumentUploadReplaceContainerProps>;
} = {},
) => {
const utils = render(
<DocumentUploadReplaceContainer
index={renderOptions?.props?.index ?? 0}
onReplaceDocumentFile={onReplaceDocumentFile}
onCancelReplaceFile={onCancelReplaceFile}
View={TestView}
></DocumentUploadReplaceContainer>,
{
...renderOptions,
},
);

return {
...utils,
};
};

beforeEach(() => {
viewProps = undefined;
});

afterEach(() => {
vi.clearAllMocks();
});

it('renders the underlying form', async () => {
const { getByText } = await setup();

expect(getByText(/Content Rendered/)).toBeVisible();
});

it('handles cancel replacement', async () => {
const {} = await setup();

await act(async () => viewProps.onCancelReplace());
expect(onCancelReplaceFile).toHaveBeenCalled();
});

it('handles file replacement on confirm', async () => {
const {} = await setup();

await act(async () => viewProps.onConfirmReplace());
expect(onReplaceDocumentFile).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState } from 'react';

import { exists } from '@/utils/utils';

import { IDocumentUploadReplaceViewProps } from './DocumentUploadReplaceView';

export interface IDocumentUploadReplaceContainerProps {
index: number;
onReplaceDocumentFile: (file: File) => void;
onCancelReplaceFile: () => void;
View: React.FunctionComponent<IDocumentUploadReplaceViewProps>;
}

const DocumentUploadReplaceContainer: React.FunctionComponent<
IDocumentUploadReplaceContainerProps
> = ({ index, onReplaceDocumentFile, onCancelReplaceFile, View }) => {
const [replacementFile, setReplacementFile] = useState<File | null>(null);

const handleOnSelectedFile = (files: File[]) => {
if (exists(files) && files.length) {
const replacementFile = files[0];
setReplacementFile(replacementFile);
}
};

return (
<View
file={replacementFile}
index={index}
onCancelReplace={() => {
setReplacementFile(null);
onCancelReplaceFile && onCancelReplaceFile();

Check warning on line 32 in source/frontend/src/features/documents/documentUpload/documentUploadReplace/DocumentUploadReplaceContainer.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=bcgov_PSP&issues=AZzEc1SsjTP5YWeqer66&open=AZzEc1SsjTP5YWeqer66&pullRequest=5245
}}
onConfirmReplace={() => onReplaceDocumentFile(replacementFile)}
onSelectedReplacementFile={handleOnSelectedFile}
></View>
);
};

export default DocumentUploadReplaceContainer;
Loading
Loading