diff --git a/plugins/communications-app/IndividualEmails/api.test.js b/plugins/communications-app/IndividualEmails/api.test.js index 4609e34a..6e0537b2 100644 --- a/plugins/communications-app/IndividualEmails/api.test.js +++ b/plugins/communications-app/IndividualEmails/api.test.js @@ -27,7 +27,7 @@ describe('getLearnersEmailInstructorTask', () => { }); }); - it('successfully fetches data', async () => { + test('successfully fetches data', async () => { const data = await getLearnersEmailInstructorTask(mockCourseId, mockSearch); expect(data).toEqual(mockResponseData); expect(getAuthenticatedHttpClient().get).toHaveBeenCalledWith( @@ -35,7 +35,7 @@ describe('getLearnersEmailInstructorTask', () => { ); }); - it('handles an error', async () => { + test('handles an error', async () => { getAuthenticatedHttpClient().get.mockRejectedValue(new Error('Network error')); await expect(getLearnersEmailInstructorTask(mockCourseId, mockSearch)).rejects.toThrow('Network error'); diff --git a/plugins/communications-app/IndividualEmails/index.jsx b/plugins/communications-app/IndividualEmails/index.jsx index 74596cdc..cfa19098 100644 --- a/plugins/communications-app/IndividualEmails/index.jsx +++ b/plugins/communications-app/IndividualEmails/index.jsx @@ -8,8 +8,9 @@ import { Container, } from '@edx/paragon'; import { Person, Close } from '@edx/paragon/icons'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import { logError } from '@edx/frontend-platform/logging'; +import { useSelector } from '@communications-app/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible/context'; import { getLearnersEmailInstructorTask } from './api'; import messages from './messages'; @@ -26,6 +27,8 @@ const IndividualEmails = ({ const [isLoading, setIsLoading] = useState(false); const [options, setOptions] = useState([]); const [inputValue] = useState([]); + const formData = useSelector((state) => state.form); + const { isFormSubmitted } = formData; const handleSearchEmailLearners = async (userEmail) => { setIsLoading(true); @@ -57,6 +60,8 @@ const IndividualEmails = ({ } }; + const isIndividualEmailsInvalid = isFormSubmitted && emailList.length === 0; + return ( @@ -81,6 +86,21 @@ const IndividualEmails = ({ )} /> + + { isIndividualEmailsInvalid && ( + + + + )} + {emailList.length > 0 && ( diff --git a/plugins/communications-app/IndividualEmails/index.test.jsx b/plugins/communications-app/IndividualEmails/index.test.jsx index fad1c904..6d79a027 100644 --- a/plugins/communications-app/IndividualEmails/index.test.jsx +++ b/plugins/communications-app/IndividualEmails/index.test.jsx @@ -34,7 +34,7 @@ describe('IndividualEmails Component', () => { ); - it('renders the component without errors', () => { + test('renders the component without errors', () => { render( @@ -42,7 +42,7 @@ describe('IndividualEmails Component', () => { ); }); - it('displays the correct internationalized messages', () => { + test('displays the correct internationalized messages', () => { render( @@ -62,7 +62,7 @@ describe('IndividualEmails Component', () => { expect(screen.getByTestId('learners-email-list-label')).toHaveTextContent(individualEmailsLabelLearnersListLabel.defaultMessage); }); - it('renders the component with main components ', () => { + test('renders the component with main components ', () => { render( @@ -78,7 +78,7 @@ describe('IndividualEmails Component', () => { expect(emailListLabel).toBeInTheDocument(); }); - it('should render two email chips', () => { + test('should render two email chips', () => { render( @@ -89,7 +89,7 @@ describe('IndividualEmails Component', () => { expect(emailChips).toHaveLength(2); }); - it('triggers search on typing in search box', async () => { + test('triggers search on typing in search box', async () => { const mockHandleEmailSelected = jest.fn(); const mockCourseId = 'course123'; render( @@ -111,7 +111,7 @@ describe('IndividualEmails Component', () => { }); }); - it('invokes handleDeleteEmail when clicking on delete icons', () => { + test('invokes handleDeleteEmail when clicking on delete icons', () => { const mockHandleDeleteEmail = jest.fn(); render( diff --git a/plugins/communications-app/RecipientsForm/index.jsx b/plugins/communications-app/RecipientsForm/index.jsx index fd16ffbd..36df889b 100644 --- a/plugins/communications-app/RecipientsForm/index.jsx +++ b/plugins/communications-app/RecipientsForm/index.jsx @@ -1,10 +1,12 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import PropTypes from 'prop-types'; +import useDeepCompareEffect from 'use-deep-compare-effect'; import { Form } from '@edx/paragon'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { useSelector, useDispatch } from '@communications-app/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible/context'; import { actionCreators as formActions } from '@communications-app/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible/context/reducer'; import PluggableComponent from '@communications-app/src/components/PluggableComponent'; +import { BulkEmailContext } from '@communications-app/src/components/bulk-email-tool/bulk-email-context'; import './styles.scss'; @@ -13,6 +15,7 @@ const recipientsFormDescription = 'A selectable choice from a list of potential const RecipientsForm = ({ cohorts: additionalCohorts, courseId }) => { const formData = useSelector((state) => state.form); + const [{ editor }] = useContext(BulkEmailContext); const dispatch = useDispatch(); const { isEditMode, @@ -62,6 +65,17 @@ const RecipientsForm = ({ cohorts: additionalCohorts, courseId }) => { setSelectedGroups(emailRecipients); }, [isEditMode, emailRecipients.length, emailRecipients]); + useDeepCompareEffect(() => { + if (!editor.editMode) { + const newSubjectValue = editor.emailSubject; + const newBodyValue = editor.emailBody; + dispatch(formActions.updateForm({ + subject: newSubjectValue, + body: newBodyValue, + })); + } + }, [editor, dispatch]); + return ( diff --git a/plugins/communications-app/RecipientsForm/package.json b/plugins/communications-app/RecipientsForm/package.json index 2b033cca..72c7c455 100644 --- a/plugins/communications-app/RecipientsForm/package.json +++ b/plugins/communications-app/RecipientsForm/package.json @@ -5,6 +5,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "dependencies": { + "use-deep-compare-effect": "^1.8.1" + }, "peerDependencies": { "@edx/frontend-app-communications": "*", "@edx/frontend-platform": "*", diff --git a/plugins/communications-app/TaskAlertModalForm/index.jsx b/plugins/communications-app/TaskAlertModalForm/index.jsx index 0c6af399..1b020ff5 100644 --- a/plugins/communications-app/TaskAlertModalForm/index.jsx +++ b/plugins/communications-app/TaskAlertModalForm/index.jsx @@ -91,8 +91,10 @@ const TaskAlertModalForm = ({ const createEmailTask = async () => { const isScheduleValid = isScheduled ? scheduleDate.length > 0 && scheduleTime.length > 0 : true; + const isIndividualEmailsValid = (emailRecipients.includes('individual-learners') && emailLearnersList.length > 0) + || !emailRecipients.includes('individual-learners'); const isFormValid = emailRecipients.length > 0 && subject.length > 0 - && body.length > 0 && isScheduleValid; + && body.length > 0 && isScheduleValid && isIndividualEmailsValid; if (isFormValid && isEditMode) { await handlePatchEmailTask();