diff --git a/src/components/Form/FormSection.jsx b/src/components/Form/FormSection.jsx index 11fe3e1a0..296b97372 100644 --- a/src/components/Form/FormSection.jsx +++ b/src/components/Form/FormSection.jsx @@ -35,7 +35,7 @@ const FormSection = ({ title, children }) => { minWidth: '50%' }} > - + {title} {children} diff --git a/src/components/Modals/ConfirmationModal.jsx b/src/components/Modals/ConfirmationModal.jsx index 9ef4c76c9..df54bcca6 100644 --- a/src/components/Modals/ConfirmationModal.jsx +++ b/src/components/Modals/ConfirmationModal.jsx @@ -22,23 +22,29 @@ import LogoutButton from './LogoutButton'; * @memberof Modals * @name ConfirmationModal * @param {object} Props - Props used for ConfirmationModal - * @param {boolean} Props.showConfirmationModal - toggle showing modal - * @param {React.Dispatch>} Props.setShowConfirmationModal + * @param {boolean} Props.showModal - toggle showing modal + * @param {React.Dispatch>} Props.setShowModal * - used to close the modal * @param {string} Props.title - text rendered in dialog title & confirmationButton * @param {string} Props.text - text rendered in dialog content text - * @param {Function} Props.confirmFunction - method that runs onClick of button + * @param {string} Props.confirmButtonText - text rendered in the confirmation button + * @param {string} Props.cancelButtonText - text rendered in the cancel button + * @param {Function} Props.onConfirm - callback that runs onClick of confirm button + * @param {Function} Props.onCancel - callback that runs onClick of cancel button and after modal is closed (optional) * @param {boolean} Props.processing - state used to disable button * @param {boolean} [Props.isLogout] - boolean to wrap button with inrupt logout * functionality * @returns {React.JSX.Element} - The confirmation modal */ const ConfirmationModal = ({ - showConfirmationModal, - setShowConfirmationModal, - title, - text, - confirmFunction, + showModal, + setShowModal, + title = 'Are you sure?', + text = 'Are you sure you want to do this?', + confirmButtonText = 'Confirm', + cancelButtonText = 'Cancel', + onConfirm = () => {}, + onCancel = () => {}, processing, isLogout = false }) => { @@ -49,21 +55,30 @@ const ConfirmationModal = ({ isLogout ? ( localStorage.clear()}> ) : ( - + ); + const handleClose = () => { + setShowModal(false); + onCancel(); + }; + return ( setShowConfirmationModal(false)} + onClose={handleClose} > {title.toUpperCase()} @@ -86,10 +101,10 @@ const ConfirmationModal = ({ variant="outlined" color="error" endIcon={} - onClick={() => setShowConfirmationModal(false)} + onClick={handleClose} fullWidth > - Cancel + {cancelButtonText} {confirmButton()} diff --git a/src/components/Modals/UploadDocumentConfirmationModal.jsx b/src/components/Modals/UploadDocumentConfirmationModal.jsx new file mode 100644 index 000000000..45c5a2675 --- /dev/null +++ b/src/components/Modals/UploadDocumentConfirmationModal.jsx @@ -0,0 +1,64 @@ +// React Imports +import React from 'react'; +// Component Imports +import ConfirmationModal from './ConfirmationModal'; + +// Display text for confirmation modal variants +const confirmationModalContentVariant = { + replace: { + title: 'Replace Document?', + text: 'A file of this name and type already exists on the pod. Would you like to replace it?', + confirmButtonText: 'Replace' + }, + add: { + title: 'Upload Document?', + text: 'Are you sure you want to upload this document?', + confirmButtonText: 'Upload' + } +}; + +/** + * UploadDocumentConfirmationModal Component - Custom ConfirmationModal + * based on the type of document upload (add or replace) that the user is attempting to perform. + * + * @memberof Modals + * @name UploadDocumentModal + * @param {object} Props - Props for UploadDocumentModal component + * @param {boolean} Props.showModal - Boolean for showing/hiding the modal + * @param {React.Dispatch>} Props.setShowModal + * - React set function for setting showModal state + * @param {string} Props.uploadType - Type of upload to perform (add or replace) + * @param {Function} Props.onConfirm - Function to run when the user confirms the upload + * @param {Function} Props.onCancel - Function to run when the user cancels the upload + * @returns {React.JSX.Element} The UploadDocumentConfirmationModal Component + */ +const UploadDocumentConfirmationModal = ({ + showModal, + setShowModal, + uploadType, + onConfirm, + onCancel +}) => { + const [isProcessing, setIsProcessing] = React.useState(false); + const modalContent = confirmationModalContentVariant[uploadType]; + + const handleConfirm = async () => { + setIsProcessing(true); + onConfirm(); + }; + + return ( + + ); +}; + +export default UploadDocumentConfirmationModal; diff --git a/src/components/Modals/UploadDocumentModal.jsx b/src/components/Modals/UploadDocumentModal.jsx index e66bb1082..d177123c0 100644 --- a/src/components/Modals/UploadDocumentModal.jsx +++ b/src/components/Modals/UploadDocumentModal.jsx @@ -23,6 +23,7 @@ import { DocumentListContext } from '@contexts'; import { DocumentSelection, FormSection } from '../Form'; import UploadButtonGroup from './UploadButtonGroup'; import useNotification from '../../hooks/useNotification'; +import UploadDocumentConfirmationModal from './UploadDocumentConfirmationModal'; /** * UploadDocumentModal Component - Component that generates the form for uploading @@ -48,6 +49,8 @@ const UploadDocumentModal = ({ showModal, setShowModal }) => { const [processing, setProcessing] = useState(false); const theme = useTheme(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const [showConfirmationModal, setShowConfirmationModal] = useState(false); + const [confirmationModalType, setConfirmationModalType] = useState('add'); const handleDocType = (event) => { setDocType(event.target.value); @@ -64,45 +67,74 @@ const UploadDocumentModal = ({ showModal, setShowModal }) => { setShowModal(false); }; - // Event handler for form/document submission to Pod - const handleDocUpload = async (e) => { - e.preventDefault(); - setProcessing(true); + const compileDocData = () => ({ + name: file.name, + type: docType, + date: expireDate, + description: docDescription + }); + + const cleanup = () => { + setShowConfirmationModal(false); + setConfirmationModalType('add'); + setProcessing(false); + clearInputFields(); + }; - const fileDesc = { - name: file.name, - type: docType, - date: expireDate, - description: docDescription - }; + const handleDocAdd = async () => { + const docData = compileDocData(); try { - await addDocument(fileDesc, file); + await addDocument(docData, file); addNotification('success', `File uploaded to Solid.`); + cleanup(); } catch (error) { - const confirmationMessage = - 'A file of this name and type already exists on the pod. Would you like to replace it?'; - switch (error.message) { case 'File already exists': - if (window.confirm(confirmationMessage)) { - await replaceDocument(fileDesc, file); - addNotification('success', `File updated on Solid.`); - } + // The confirmation modal will prompt the user to replace the file or not + setConfirmationModalType('replace'); + setShowConfirmationModal(true); break; default: addNotification('error', `File failed to upload. Reason: ${error.message}`); } - } finally { - setProcessing(false); - clearInputFields(); } }; + const handleDocReplace = async () => { + const docData = compileDocData(); + + try { + await replaceDocument(docData, file); + addNotification('success', `File updated on Solid.`); + cleanup(); + } catch (error) { + addNotification('error', `File failed to upload. Reason: ${error.message}`); + } + }; + + const handleUploadCancelled = () => { + setProcessing(false); + }; + + // Event handler for form/document submission to Pod + const onFormSubmit = async (e) => { + e.preventDefault(); + setProcessing(true); + handleDocAdd(); + }; + return ( + -
+ } label="Verify file on upload" diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index a3080f73a..3311a9a7b 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -47,11 +47,12 @@ const NavBar = () => { {isSmallScreen && } {isLargeScreen && } diff --git a/src/pages/Contacts.jsx b/src/pages/Contacts.jsx index 2fb766646..11f6a3ce8 100644 --- a/src/pages/Contacts.jsx +++ b/src/pages/Contacts.jsx @@ -91,11 +91,12 @@ const Contacts = () => { addContact={addContact} /> diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index 81c7eb7c6..b0080ca4f 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -213,11 +213,12 @@ const Profile = () => { dataset={dataset} /> diff --git a/test/components/Modals/ConfirmationModal.test.jsx b/test/components/Modals/ConfirmationModal.test.jsx index 5425e98b5..5f80b97da 100644 --- a/test/components/Modals/ConfirmationModal.test.jsx +++ b/test/components/Modals/ConfirmationModal.test.jsx @@ -1,17 +1,20 @@ import React from 'react'; import { ConfirmationModal } from '@components/Modals'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { render } from '@testing-library/react'; import createMatchMedia from '../../helpers/createMatchMedia'; +import '@testing-library/jest-dom/extend-expect'; -const MockConfirmationModal = () => ; +const MockConfirmationModalBasic = () => ( + +); const MockConfirmationModalLogout = () => ( - + ); describe('Default screen', () => { it('renders button container flex-direction as row default', () => { - const { getByRole } = render(); + const { getByRole } = render(); const cancelButton = getByRole('button', { name: 'Cancel' }); const buttonContainer = cancelButton.parentElement; const cssProperty = getComputedStyle(buttonContainer); @@ -21,8 +24,8 @@ describe('Default screen', () => { it('renders logout button container flex-direction as row default', () => { const { getByRole } = render(); - const cancelButton = getByRole('button', { name: 'Cancel' }); - const buttonContainer = cancelButton.parentElement; + const confirmButton = getByRole('button', { name: 'Log Out' }); + const buttonContainer = confirmButton.parentElement; const cssProperty = getComputedStyle(buttonContainer); expect(cssProperty.flexDirection).toBe('row'); @@ -32,7 +35,7 @@ describe('Default screen', () => { describe('Mobile screen', () => { it('renders button container flex-direction as column mobile', () => { window.matchMedia = createMatchMedia(599); - const { getByRole } = render(); + const { getByRole } = render(); const cancelButton = getByRole('button', { name: 'Cancel' }); const buttonContainer = cancelButton.parentElement; const cssProperty = getComputedStyle(buttonContainer); @@ -42,10 +45,74 @@ describe('Mobile screen', () => { it('renders logout button container flex-direction as column mobile', () => { const { getByRole } = render(); - const cancelButton = getByRole('button', { name: 'Cancel' }); - const buttonContainer = cancelButton.parentElement; + const confirmButton = getByRole('button', { name: 'Log Out' }); + const buttonContainer = confirmButton.parentElement; const cssProperty = getComputedStyle(buttonContainer); expect(cssProperty.flexDirection).toBe('column'); }); }); + +describe('Renders correct text for: ', () => { + it('title', () => { + const customTitle = 'CUSTOM TITLE'; + const { getByRole } = render(); + const title = getByRole('heading', { name: customTitle }); + expect(title).toBeInTheDocument(); + }); + it('text', () => { + const customText = 'Custom Text'; + const { getByText } = render(); + const text = getByText(customText); + expect(text).toBeInTheDocument(); + }); + it('confirm button', () => { + const customConfirmButtonText = 'Custom Confirm Button Text'; + const { getByRole } = render( + + ); + const confirmButton = getByRole('button', { name: customConfirmButtonText }); + expect(confirmButton).toBeInTheDocument(); + }); + it('cancel button', () => { + const customCancelButtonText = 'Custom Cancel Button Text'; + const { getByRole } = render( + + ); + const cancelButton = getByRole('button', { name: customCancelButtonText }); + expect(cancelButton).toBeInTheDocument(); + }); +}); + +it('triggers custom confirm action on click', () => { + const customConfirmButtonText = 'Custom Confirm Button Text'; + const customConfirmButtonAction = vi.fn(); + const { getByRole } = render( + + ); + const confirmButton = getByRole('button', { name: customConfirmButtonText }); + confirmButton.click(); + expect(customConfirmButtonAction).toHaveBeenCalled(); +}); + +it('triggers custom cancel action on click', () => { + const customCancelButtonText = 'Custom Cancel Button Text'; + const customCancelButtonAction = vi.fn(); + const setShowModal = vi.fn(); + const { getByRole } = render( + + ); + const cancelButton = getByRole('button', { name: customCancelButtonText }); + cancelButton.click(); + expect(setShowModal).toHaveBeenCalledWith(false); + expect(customCancelButtonAction).toHaveBeenCalled(); +}); diff --git a/test/components/Modals/UploadDocumentModal.test.jsx b/test/components/Modals/UploadDocumentModal.test.jsx index 6d476de10..2180cb705 100644 --- a/test/components/Modals/UploadDocumentModal.test.jsx +++ b/test/components/Modals/UploadDocumentModal.test.jsx @@ -3,6 +3,7 @@ import { expect, it } from 'vitest'; import { render } from '@testing-library/react'; import { UploadDocumentModal } from '@components/Modals'; import createMatchMedia from '../../helpers/createMatchMedia'; +import '@testing-library/jest-dom/extend-expect'; const MockUploadDocumentModal = () => ; @@ -24,3 +25,21 @@ it('renders cancel/upload group as column mobile', () => { expect(cssProperty.flexDirection).toBe('column'); }); + +it('renders initial state and elements correctly', () => { + const { getByRole, getByLabelText } = render(); + + // Check for initial state values + const modalTitle = getByRole('heading', { name: 'Upload Document' }); + const fileTypeInput = getByRole('combobox'); + const expirationDateInput = getByLabelText('Expiration Date'); + const descriptionInput = getByLabelText('Enter Description'); + const fileUploadButton = getByRole('button', { name: 'Choose file' }); + + // Check for the presence of specific elements + expect(modalTitle).toBeInTheDocument(); + expect(fileTypeInput).toBeInTheDocument(); + expect(expirationDateInput).toBeInTheDocument(); + expect(descriptionInput).toBeInTheDocument(); + expect(fileUploadButton).toBeInTheDocument(); +});