diff --git a/src/certificates/certificate-create-form/CertificateCreateForm.jsx b/src/certificates/certificate-create-form/CertificateCreateForm.jsx index 091f6090d0..70aa7ad4d0 100644 --- a/src/certificates/certificate-create-form/CertificateCreateForm.jsx +++ b/src/certificates/certificate-create-form/CertificateCreateForm.jsx @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import { Card, Stack, Button } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Formik, Form, FieldArray } from 'formik'; import CertificateDetailsForm from '../certificate-details/CertificateDetailsForm'; @@ -9,8 +10,9 @@ import messages from '../messages'; import useCertificateCreateForm from './hooks/useCertificateCreateForm'; const CertificateCreateForm = ({ courseId }) => { + const intl = useIntl(); const { - intl, courseTitle, handleCertificateSubmit, handleFormCancel, + courseTitle, handleCertificateSubmit, handleFormCancel, } = useCertificateCreateForm(courseId); return ( diff --git a/src/certificates/certificate-create-form/hooks/useCertificateCreateForm.jsx b/src/certificates/certificate-create-form/hooks/useCertificateCreateForm.jsx index 5d895de5fe..129427059c 100644 --- a/src/certificates/certificate-create-form/hooks/useCertificateCreateForm.jsx +++ b/src/certificates/certificate-create-form/hooks/useCertificateCreateForm.jsx @@ -1,5 +1,4 @@ import { useSelector, useDispatch } from 'react-redux'; -import { useIntl } from '@edx/frontend-platform/i18n'; import { MODE_STATES } from '../../data/constants'; import { getCourseTitle } from '../../data/selectors'; @@ -7,7 +6,6 @@ import { setMode } from '../../data/slice'; import { createCourseCertificate } from '../../data/thunks'; const useCertificateCreateForm = (courseId) => { - const intl = useIntl(); const dispatch = useDispatch(); const courseTitle = useSelector(getCourseTitle); @@ -21,7 +19,7 @@ const useCertificateCreateForm = (courseId) => { window.scrollTo({ top: 0, behavior: 'smooth' }); }; return { - intl, courseTitle, handleCertificateSubmit, handleFormCancel, + courseTitle, handleCertificateSubmit, handleFormCancel, }; }; diff --git a/src/certificates/certificate-edit-form/CertificateEditForm.jsx b/src/certificates/certificate-edit-form/CertificateEditForm.jsx index a7bf513748..b02af4319f 100644 --- a/src/certificates/certificate-edit-form/CertificateEditForm.jsx +++ b/src/certificates/certificate-edit-form/CertificateEditForm.jsx @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import { Card, Stack, Button } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Formik, Form, FieldArray } from 'formik'; import ModalNotification from '../../generic/modal-notification'; @@ -10,8 +11,8 @@ import messages from '../certificate-details/messages'; import useCertificateEditForm from './hooks/useCertificateEditForm'; const CertificateEditForm = ({ courseId }) => { + const intl = useIntl(); const { - intl, confirmOpen, courseTitle, certificates, diff --git a/src/certificates/certificate-edit-form/CertificateEditForm.test.jsx b/src/certificates/certificate-edit-form/CertificateEditForm.test.jsx new file mode 100644 index 0000000000..df2ff4bde0 --- /dev/null +++ b/src/certificates/certificate-edit-form/CertificateEditForm.test.jsx @@ -0,0 +1,117 @@ +import { Provider } from 'react-redux'; +import { render, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import MockAdapter from 'axios-mock-adapter'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +import { executeThunk } from '../../utils'; +import initializeStore from '../../store'; +import { getCertificatesApiUrl, getUpdateCertificateApiUrl } from '../data/api'; +import { fetchCertificates, deleteCourseCertificate, updateCourseCertificate } from '../data/thunks'; +import { certificatesDataMock } from '../__mocks__'; +import { MODE_STATES } from '../data/constants'; +import messagesDetails from '../certificate-details/messages'; +import messages from '../messages'; +import CertificateEditForm from './CertificateEditForm'; + +let axiosMock; +let store; +const courseId = 'course-123'; + +jest.mock('@edx/frontend-platform/i18n', () => ({ + ...jest.requireActual('@edx/frontend-platform/i18n'), + useIntl: () => ({ + formatMessage: (message) => message.defaultMessage, + }), +})); + +const renderComponent = () => render( + + + + + , +); + +const initialState = { + certificates: { + certificatesData: {}, + componentMode: MODE_STATES.editAll, + }, +}; + +describe('CertificatesList Component', () => { + beforeEach(async () => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(initialState); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getCertificatesApiUrl(courseId)) + .reply(200, certificatesDataMock); + await executeThunk(fetchCertificates(courseId), store.dispatch); + }); + + it('submits the form with updated certificate details', async () => { + const courseTitleOverrideValue = 'Updated Course Title'; + const signatoryNameValue = 'Updated signatory name'; + const newCertificateData = { + ...certificatesDataMock, + courseTitle: courseTitleOverrideValue, + certificates: [{ + ...certificatesDataMock.certificates[0], + signatories: [{ + ...certificatesDataMock.certificates[0].signatories[0], + name: signatoryNameValue, + }], + }], + }; + + const { getByDisplayValue, getByRole, getByPlaceholderText } = renderComponent(); + + userEvent.type( + getByPlaceholderText(messagesDetails.detailsCourseTitleOverride.defaultMessage), + courseTitleOverrideValue, + ); + + userEvent.click(getByRole('button', { name: messages.saveTooltip.defaultMessage })); + + axiosMock.onPost( + getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id), + ).reply(200, newCertificateData); + await executeThunk(updateCourseCertificate(courseId, newCertificateData), store.dispatch); + + await waitFor(() => { + expect(getByDisplayValue( + certificatesDataMock.certificates[0].courseTitle + courseTitleOverrideValue, + )).toBeInTheDocument(); + }); + }); + + it('deletes a certificate and updates the store', async () => { + axiosMock.onDelete( + getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id), + ).reply(200); + + const { getByRole } = renderComponent(); + + userEvent.click(getByRole('button', { name: messages.deleteTooltip.defaultMessage })); + + const confirmDeleteModal = getByRole('dialog'); + userEvent.click(within(confirmDeleteModal).getByRole('button', { name: messages.deleteTooltip.defaultMessage })); + + await executeThunk(deleteCourseCertificate(courseId, certificatesDataMock.certificates[0].id), store.dispatch); + + await waitFor(() => { + expect(store.getState().certificates.certificatesData.certificates.length).toBe(0); + }); + }); +}); diff --git a/src/certificates/certificate-edit-form/hooks/useCertificateEditForm.jsx b/src/certificates/certificate-edit-form/hooks/useCertificateEditForm.jsx index d2f5dc423f..5baf241a87 100644 --- a/src/certificates/certificate-edit-form/hooks/useCertificateEditForm.jsx +++ b/src/certificates/certificate-edit-form/hooks/useCertificateEditForm.jsx @@ -1,4 +1,3 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; import { useSelector, useDispatch } from 'react-redux'; import { useToggle } from '@openedx/paragon'; @@ -9,7 +8,6 @@ import { updateCourseCertificate, deleteCourseCertificate } from '../../data/thu import { defaultCertificate } from '../../constants'; const useCertificateEditForm = (courseId) => { - const intl = useIntl(); const dispatch = useDispatch(); const [isConfirmOpen, confirmOpen, confirmClose] = useToggle(false); const courseTitle = useSelector(getCourseTitle); @@ -36,7 +34,6 @@ const useCertificateEditForm = (courseId) => { })); return { - intl, confirmOpen, courseTitle, certificates, diff --git a/src/certificates/certificate-signatories/CertificateSignatories.jsx b/src/certificates/certificate-signatories/CertificateSignatories.jsx index ef4e03c370..d290ac5e61 100644 --- a/src/certificates/certificate-signatories/CertificateSignatories.jsx +++ b/src/certificates/certificate-signatories/CertificateSignatories.jsx @@ -10,7 +10,15 @@ import useCreateSignatory from './hooks/useCreateSignatory'; import messages from './messages'; const CertificateSignatories = ({ - signatories, handleChange, handleBlur, arrayHelpers, setFieldValue, isForm, resetForm, editModes, setEditModes, + isForm, + editModes, + signatories, + arrayHelpers, + initialSignatoriesValues, + setFieldValue, + setEditModes, + handleBlur, + handleChange, }) => { const intl = useIntl(); @@ -19,7 +27,7 @@ const CertificateSignatories = ({ handleDeleteSignatory, handleCancelUpdateSignatory, } = useEditSignatory({ - arrayHelpers, editModes, setEditModes, resetForm, + arrayHelpers, editModes, setEditModes, setFieldValue, initialSignatoriesValues, }); const { handleAddSignatory } = useCreateSignatory({ arrayHelpers }); @@ -90,18 +98,23 @@ CertificateSignatories.defaultProps = { handleBlur: null, setFieldValue: null, arrayHelpers: null, - resetForm: null, isForm: false, editModes: {}, setEditModes: null, + initialSignatoriesValues: null, }; CertificateSignatories.propTypes = { isForm: PropTypes.bool, editModes: PropTypes.objectOf(PropTypes.bool), + initialSignatoriesValues: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string.isRequired, + organization: PropTypes.string.isRequired, + signatureImagePath: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + })), handleChange: PropTypes.func, handleBlur: PropTypes.func, - resetForm: PropTypes.func, setFieldValue: PropTypes.func, setEditModes: PropTypes.func, arrayHelpers: PropTypes.shape({}), diff --git a/src/certificates/certificate-signatories/hooks/useEditSignatory.jsx b/src/certificates/certificate-signatories/hooks/useEditSignatory.jsx index 44928f9563..5784169237 100644 --- a/src/certificates/certificate-signatories/hooks/useEditSignatory.jsx +++ b/src/certificates/certificate-signatories/hooks/useEditSignatory.jsx @@ -1,5 +1,5 @@ const useEditSignatory = ({ - arrayHelpers, editModes, setEditModes, resetForm, + arrayHelpers, editModes, setEditModes, setFieldValue, initialSignatoriesValues, }) => { const handleDeleteSignatory = (id) => { arrayHelpers.remove(id); @@ -19,8 +19,12 @@ const useEditSignatory = ({ }; const handleCancelUpdateSignatory = (id) => { + const signatoryInitialValues = initialSignatoriesValues[id]; + Object.keys(signatoryInitialValues).forEach(fieldKey => { + const fieldName = `signatories[${id}].${fieldKey}`; + setFieldValue(fieldName, signatoryInitialValues[fieldKey]); + }); toggleEditSignatory(id); - resetForm(); }; return { toggleEditSignatory, handleDeleteSignatory, handleCancelUpdateSignatory }; diff --git a/src/certificates/certificates-list/CertificatesList.jsx b/src/certificates/certificates-list/CertificatesList.jsx index c1497c2898..9e394c3e98 100644 --- a/src/certificates/certificates-list/CertificatesList.jsx +++ b/src/certificates/certificates-list/CertificatesList.jsx @@ -23,7 +23,7 @@ const CertificatesList = ({ courseId }) => { {certificates.map((certificate, idx) => ( {({ - values, handleChange, handleBlur, resetForm, setFieldValue, + values, handleChange, handleBlur, setFieldValue, }) => (
@@ -43,10 +43,10 @@ const CertificatesList = ({ courseId }) => { signatories={values.signatories} arrayHelpers={arrayHelpers} editModes={editModes} + initialSignatoriesValues={initialValues[idx].signatories} handleChange={handleChange} handleBlur={handleBlur} setFieldValue={setFieldValue} - resetForm={resetForm} setEditModes={setEditModes} /> )}