diff --git a/src/features/Classes/EnrollStudent/__test__/index.test.jsx b/src/features/Classes/EnrollStudent/__test__/index.test.jsx index 4c42cb3..9941962 100644 --- a/src/features/Classes/EnrollStudent/__test__/index.test.jsx +++ b/src/features/Classes/EnrollStudent/__test__/index.test.jsx @@ -13,7 +13,8 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('features/Students/data/api', () => ({ - handleEnrollments: jest.fn(() => Promise.resolve()), + handleEnrollments: jest.fn().mockReturnValue({}), + getMessages: jest.fn().mockReturnValue({}), })); jest.mock('@edx/frontend-platform/logging', () => ({ @@ -21,9 +22,13 @@ jest.mock('@edx/frontend-platform/logging', () => ({ })); describe('EnrollStudent', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + test('Should render with correct elements', () => { const { getByText, getByPlaceholderText } = renderWithProviders( - {}} />, + {}} queryClassId="ccx1" />, { preloadedState: {} }, ); @@ -37,14 +42,14 @@ describe('EnrollStudent', () => { const onCloseMock = jest.fn(); const { getByPlaceholderText, getByText } = renderWithProviders( - , + , { preloadedState: {} }, ); const handleEnrollmentsMock = jest.spyOn(api, 'handleEnrollments').mockResolvedValue({ data: { results: [{ - tags: 'success', + identifier: 'test@example.com', }], }, }); @@ -56,7 +61,7 @@ describe('EnrollStudent', () => { fireEvent.click(submitButton); await waitFor(() => { - expect(getByText('Email invite has been sent successfully')).toBeInTheDocument(); + expect(getByText('Successfully enrolled and sent email to the following users: test@example.com')).toBeInTheDocument(); }); expect(handleEnrollmentsMock).toHaveBeenCalledTimes(1); @@ -66,14 +71,14 @@ describe('EnrollStudent', () => { test('Should handle form submission and show error toast', async () => { const onCloseMock = jest.fn(); - const handleEnrollmentsMock = jest.spyOn(api, 'handleEnrollments').mockResolvedValue({ + const messagesApiMock = jest.spyOn(api, 'getMessages').mockResolvedValue({ data: { results: [{ tags: 'error', message: 'Enrollment limit reached' }], }, }); const { getByPlaceholderText, getByText } = renderWithProviders( - , + , { preloadedState: {} }, ); @@ -87,8 +92,8 @@ describe('EnrollStudent', () => { expect(getByText('Enrollment limit reached')).toBeInTheDocument(); }); - expect(handleEnrollmentsMock).toHaveBeenCalledTimes(1); + expect(messagesApiMock).toHaveBeenCalledTimes(1); - handleEnrollmentsMock.mockRestore(); + messagesApiMock.mockRestore(); }); }); diff --git a/src/features/Classes/EnrollStudent/index.jsx b/src/features/Classes/EnrollStudent/index.jsx index 035bbab..a858926 100644 --- a/src/features/Classes/EnrollStudent/index.jsx +++ b/src/features/Classes/EnrollStudent/index.jsx @@ -15,7 +15,7 @@ import { Button } from 'react-paragon-topaz'; import { logError } from '@edx/frontend-platform/logging'; import { fetchStudentsData } from 'features/Students/data'; -import { handleEnrollments } from 'features/Students/data/api'; +import { handleEnrollments, getMessages } from 'features/Students/data/api'; import { fetchAllClassesData } from 'features/Classes/data/thunks'; import { initialPage } from 'features/constants'; @@ -47,20 +47,47 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => { try { setLoading(true); const response = await handleEnrollments(formData, queryClassId); + const validationEmailList = response?.data?.results; + const messages = await getMessages(); + let validEmails = []; + let invalidEmails = []; + let textToast = ''; /** * This is because the service that checks the enrollment status is a different * endpoint, and that endpoint always returns a status 200, so the error cannot be * caught with a .catch. */ - if (response?.data?.results[0]?.tags === 'error') { - setToastMessage(decodeURIComponent(response?.data?.results[0]?.message)); + if (messages?.data?.results[0]?.tags === 'error') { + setToastMessage(decodeURIComponent(messages?.data?.results[0]?.message)); setShowToast(true); return onClose(); } - setToastMessage('Email invite has been sent successfully'); + validationEmailList.map(email => { + if (email?.invalidIdentifier) { + invalidEmails = [ + ...invalidEmails, + email.identifier, + ]; + return invalidEmails; + } + validEmails = [ + ...validEmails, + email.identifier, + ]; + return validEmails; + }); + + if (invalidEmails.length > 0) { + textToast = `The following email adresses are invalid: ${invalidEmails.join(' ')}\n`; + } + if (validEmails.length > 0) { + textToast += `Successfully enrolled and sent email to the following users: ${validEmails.join(' ')}`; + } + + setToastMessage(textToast); const params = { course_name: courseNameDecoded, @@ -84,7 +111,7 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => { return ( <> - setShowToast(false)} show={showToast}> + setShowToast(false)} show={showToast} className="toast-message"> {toastMessage} {
diff --git a/src/features/Classes/EnrollStudent/index.scss b/src/features/Classes/EnrollStudent/index.scss index a95c57a..fadadf3 100644 --- a/src/features/Classes/EnrollStudent/index.scss +++ b/src/features/Classes/EnrollStudent/index.scss @@ -7,4 +7,12 @@ .body-container { min-height: 193px; + + .student-email .pgn__form-control-floating-label { + display: block; + } +} + +.toast-message { + white-space: pre-wrap; } diff --git a/src/features/Students/data/api.js b/src/features/Students/data/api.js index c03cd07..4390d69 100644 --- a/src/features/Students/data/api.js +++ b/src/features/Students/data/api.js @@ -21,9 +21,13 @@ function handleEnrollments(data, courseId) { return getAuthenticatedHttpClient().post( `${INSTRUCTOR_API_URL.replace(courseIdSearchPattern, courseId)}/students_update_enrollment`, data, - ).then(() => getAuthenticatedHttpClient().get( + ); +} + +function getMessages() { + return getAuthenticatedHttpClient().get( `${getConfig().LMS_BASE_URL}/pearson_course_operation/api/messages/get-messages/`, - )); + ); } function getStudentsMetrics(institutionId, days) { @@ -55,4 +59,5 @@ export { handleEnrollments, getStudentsMetrics, getClassesMetrics, + getMessages, };