From 8ac52e74589c4d86743040b13972f140b93ad940 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Sun, 8 Dec 2024 23:15:25 -0500 Subject: [PATCH] fix: include PR optimizations --- .../course-cards/InProgressCourseCard.jsx | 2 +- .../course-cards/SavedForLaterCourseCard.jsx | 2 +- .../course-cards/unenroll/UnenrollModal.jsx | 33 ++- .../unenroll/UnenrollModal.test.jsx | 166 ++++++-------- .../course-enrollments/data/hooks.js | 102 ++++----- .../data/tests/hooks.test.jsx | 207 ++++++++++-------- .../UserEnrollmentForm.jsx | 9 +- .../UserEnrollmentForm.test.jsx | 36 +-- 8 files changed, 269 insertions(+), 288 deletions(-) diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx index d47c47fada..1454f8baa5 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx @@ -85,7 +85,7 @@ export const InProgressCourseCard = ({ const [isMarkCompleteModalOpen, setIsMarkCompleteModalOpen] = useState(false); const { courseCards } = useContext(AppContext); const { data: enterpriseCustomer } = useEnterpriseCustomer(); - const updateCourseEnrollmentStatus = useUpdateCourseEnrollmentStatus({ enterpriseCustomer }); + const updateCourseEnrollmentStatus = useUpdateCourseEnrollmentStatus(); const isExecutiveEducation = EXECUTIVE_EDUCATION_COURSE_MODES.includes(mode); const coursewareOrUpgradeLink = useLinkToCourse({ linkToCourse, diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx index 8747bb2f3c..df80ee9eae 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx @@ -35,7 +35,7 @@ const SavedForLaterCourseCard = (props) => { const navigate = useNavigate(); const { data: enterpriseCustomer } = useEnterpriseCustomer(); - const updateCourseEnrollmentStatus = useUpdateCourseEnrollmentStatus({ enterpriseCustomer }); + const updateCourseEnrollmentStatus = useUpdateCourseEnrollmentStatus(); const [isModalOpen, setIsModalOpen] = useState(false); const handleMoveToInProgressOnClose = () => { diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx index 9d646fba22..bc8dc6c042 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx @@ -40,22 +40,21 @@ const UnenrollModal = ({ onClose(); }; - // TODO: There is opportunity to generalize this approach into a helper function - const updateQueryForUnenrollment = () => { + const updateQueriesAfterUnenrollment = () => { const enrollmentForCourseFilter = (enrollment) => enrollment.courseRunId !== courseRunId; const isBFFEnabled = isBFFEnabledForEnterpriseCustomer(enterpriseCustomer.uuid); if (isBFFEnabled) { // Determine which BFF queries need to be updated after unenrolling. - const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF( - { enterpriseSlug: params.enterpriseSlug }, - ).queryKey; + const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF({ + enterpriseSlug: params.enterpriseSlug, + }).queryKey; const bffQueryKeysToUpdate = [dashboardBFFQueryKey]; // Update the enterpriseCourseEnrollments data in the cache for each BFF query. bffQueryKeysToUpdate.forEach((queryKey) => { const existingBFFData = queryClient.getQueryData(queryKey); if (!existingBFFData) { - logInfo(`Skipping optimistic cache update of ${queryKey} as no cached query data exists yet.`); + logInfo(`Skipping optimistic cache update of ${JSON.stringify(queryKey)} as no cached query data exists yet.`); return; } const updatedBFFData = { @@ -64,17 +63,17 @@ const UnenrollModal = ({ }; queryClient.setQueryData(queryKey, updatedBFFData); }); - } else { - // Update the legacy queryEnterpriseCourseEnrollments cache as well. - const enterpriseCourseEnrollmentsQueryKey = queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid).queryKey; - const existingCourseEnrollmentsData = queryClient.getQueryData(enterpriseCourseEnrollmentsQueryKey); - if (!existingCourseEnrollmentsData) { - logInfo('Skipping optimistic cache update of {existingCourseEnrollmentsData} as no cached query data exists yet.'); - return; - } - const updatedCourseEnrollmentsData = existingCourseEnrollmentsData.filter(enrollmentForCourseFilter); - queryClient.setQueryData(enterpriseCourseEnrollmentsQueryKey, updatedCourseEnrollmentsData); } + + // Update the legacy queryEnterpriseCourseEnrollments cache as well. + const enterpriseCourseEnrollmentsQueryKey = queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid).queryKey; + const existingCourseEnrollmentsData = queryClient.getQueryData(enterpriseCourseEnrollmentsQueryKey); + if (!existingCourseEnrollmentsData) { + logInfo(`Skipping optimistic cache update of ${JSON.stringify(enterpriseCourseEnrollmentsQueryKey)} as no cached query data exists yet.`); + return; + } + const updatedCourseEnrollmentsData = existingCourseEnrollmentsData.filter(enrollmentForCourseFilter); + queryClient.setQueryData(enterpriseCourseEnrollmentsQueryKey, updatedCourseEnrollmentsData); }; const handleUnenrollButtonClick = async () => { @@ -87,7 +86,7 @@ const UnenrollModal = ({ setBtnState('default'); return; } - updateQueryForUnenrollment(); + updateQueriesAfterUnenrollment(); addToast('You have been unenrolled from the course.'); onSuccess(); }; diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.test.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.test.jsx index fec2b3e158..fa93ef5a15 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.test.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.test.jsx @@ -8,7 +8,6 @@ import { unenrollFromCourse } from './data'; import UnenrollModal from './UnenrollModal'; import { ToastsContext } from '../../../../../Toasts'; import { - fetchEnterpriseLearnerDashboard, isBFFEnabledForEnterpriseCustomer, learnerDashboardBFFResponse, queryEnterpriseCourseEnrollments, @@ -47,9 +46,10 @@ jest.mock('@edx/frontend-platform/logging', () => ({ const mockEnterpriseCustomer = enterpriseCustomerFactory(); const mockEnterpriseCourseEnrollment = enterpriseCourseEnrollmentFactory(); -const mockBFFEnterpriseCourseEnrollments = { +const mockEnterpriseCourseEnrollments = [mockEnterpriseCourseEnrollment]; +const mockBFFDashboardDataWithEnrollments = { ...learnerDashboardBFFResponse, - enterpriseCourseEnrollments: [mockEnterpriseCourseEnrollment], + enterpriseCourseEnrollments: mockEnterpriseCourseEnrollments, }; const mockOnClose = jest.fn(); @@ -67,21 +67,21 @@ const mockAddToast = jest.fn(); let mockQueryClient; const UnenrollModalWrapper = ({ - enterpriseCourseEnrollmentsData = mockEnterpriseCourseEnrollment, - bffEnterpriseCourseEnrollmentsData = mockBFFEnterpriseCourseEnrollments, + existingEnrollmentsQueryData = mockEnterpriseCourseEnrollments, + existingBFFDashboardQueryData = mockBFFDashboardDataWithEnrollments, ...props }) => { mockQueryClient = queryClient(); - if (enterpriseCourseEnrollmentsData) { + if (existingEnrollmentsQueryData) { mockQueryClient.setQueryData( queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, - [enterpriseCourseEnrollmentsData], + existingEnrollmentsQueryData, ); } - if (bffEnterpriseCourseEnrollmentsData) { + if (existingBFFDashboardQueryData) { mockQueryClient.setQueryData( queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: 'test-enterprise-slug' }).queryKey, - bffEnterpriseCourseEnrollmentsData, + existingBFFDashboardQueryData, ); } return ( @@ -130,139 +130,107 @@ describe('', () => { expect(mockOnClose).toHaveBeenCalledTimes(1); }); - test('should handle unenroll click, non-BFF', async () => { - unenrollFromCourse.mockResolvedValueOnce(); - const props = { - ...baseUnenrollModalProps, - isOpen: true, - }; - render(); - userEvent.click(screen.getByText('Unenroll')); - - await waitFor(() => { - const updatedEnrollments = mockQueryClient.getQueryData( - queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, - ); - expect(updatedEnrollments).toEqual([]); - expect(mockOnSuccess).toHaveBeenCalledTimes(1); - expect(mockAddToast).toHaveBeenCalledTimes(1); - expect(mockAddToast).toHaveBeenCalledWith('You have been unenrolled from the course.'); - }); - }); - - test('should handle unenroll click, BFF', async () => { - fetchEnterpriseLearnerDashboard.mockResolvedValueOnce(learnerDashboardBFFResponse); - isBFFEnabledForEnterpriseCustomer.mockReturnValue(true); - const props = { - ...baseUnenrollModalProps, - isOpen: true, - }; - render(); - userEvent.click(screen.getByText('Unenroll')); - - await waitFor(() => { - const updatedEnrollments = mockQueryClient.getQueryData( - queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: 'test-enterprise-slug' }).queryKey, - ); - expect(updatedEnrollments).toEqual(learnerDashboardBFFResponse); - expect(mockOnSuccess).toHaveBeenCalledTimes(1); - expect(mockAddToast).toHaveBeenCalledTimes(1); - expect(mockAddToast).toHaveBeenCalledWith('You have been unenrolled from the course.'); - }); - }); - test.each([ // BFF enabled { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: true, + existingBFFDashboardQueryData: mockBFFDashboardDataWithEnrollments, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: true, + existingBFFDashboardQueryData: mockBFFDashboardDataWithEnrollments, + existingEnrollmentsQueryData: null, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: true, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: true, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: null, }, // BFF disabled { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: false, + existingBFFDashboardQueryData: mockBFFDashboardDataWithEnrollments, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: false, + existingBFFDashboardQueryData: mockBFFDashboardDataWithEnrollments, + existingEnrollmentsQueryData: null, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: false, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: false, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: null, }, - ])('should handle unenroll click, with empty dataset (%s)', async ({ - bffEnterpriseCourseEnrollmentsData, - enterpriseCourseEnrollmentsData, + ])('should handle unenroll click (%s)', async ({ isBFFEnabled, + existingBFFDashboardQueryData, + existingEnrollmentsQueryData, }) => { - fetchEnterpriseLearnerDashboard.mockResolvedValueOnce(learnerDashboardBFFResponse); - unenrollFromCourse.mockResolvedValueOnce(); isBFFEnabledForEnterpriseCustomer.mockReturnValue(isBFFEnabled); + unenrollFromCourse.mockResolvedValueOnce(); const props = { ...baseUnenrollModalProps, isOpen: true, - bffEnterpriseCourseEnrollmentsData, - enterpriseCourseEnrollmentsData, + existingBFFDashboardQueryData, + existingEnrollmentsQueryData, }; render(); userEvent.click(screen.getByText('Unenroll')); await waitFor(() => { - let updatedEnrollments; + const bffDashboardData = mockQueryClient.getQueryData( + queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: 'test-enterprise-slug' }).queryKey, + ); + let expectedLogInfoCalls = 0; if (isBFFEnabled) { - updatedEnrollments = mockQueryClient.getQueryData( - queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: 'test-enterprise-slug' }).queryKey, - ); - if (bffEnterpriseCourseEnrollmentsData && !enterpriseCourseEnrollmentsData) { - expect(updatedEnrollments).toEqual(learnerDashboardBFFResponse); - expect(logInfo).toHaveBeenCalledTimes(0); - } else if (!bffEnterpriseCourseEnrollmentsData) { - expect(updatedEnrollments).toEqual(undefined); - expect(logInfo).toHaveBeenCalledTimes(1); + // Only verify the BFF queryEnterpriseCourseEnrollments cache is updated if BFF feature is enabled. + let expectedBFFDashboardData; + if (existingBFFDashboardQueryData) { + expectedBFFDashboardData = learnerDashboardBFFResponse; } else { - expect(updatedEnrollments).toEqual(learnerDashboardBFFResponse); - expect(logInfo).toHaveBeenCalledTimes(0); + expectedLogInfoCalls += 1; } - } - if (!isBFFEnabled) { - updatedEnrollments = mockQueryClient.getQueryData( - queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, - ); - if (enterpriseCourseEnrollmentsData && !bffEnterpriseCourseEnrollmentsData) { - expect(updatedEnrollments).toEqual([]); - expect(logInfo).toHaveBeenCalledTimes(0); - } else if (!enterpriseCourseEnrollmentsData) { - expect(updatedEnrollments).toEqual(undefined); - expect(logInfo).toHaveBeenCalledTimes(1); - } else { - expect(updatedEnrollments).toEqual([]); - expect(logInfo).toHaveBeenCalledTimes(0); + expect(bffDashboardData).toEqual(expectedBFFDashboardData); + } else { + let expectedBFFDashboardData; + if (existingBFFDashboardQueryData) { + expectedBFFDashboardData = existingBFFDashboardQueryData; } + // Without BFF feature enabled, the original query cache data should remain, if any. + expect(bffDashboardData).toEqual(expectedBFFDashboardData); + } + + // Always verify the legacy queryEnterpriseCourseEnrollments cache is updated. + const legacyEnrollmentsData = mockQueryClient.getQueryData( + queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, + ); + let expectedLegacyEnrollmentsData; + if (existingEnrollmentsQueryData) { + expectedLegacyEnrollmentsData = []; + } else { + expectedLogInfoCalls += 1; } + expect(legacyEnrollmentsData).toEqual(expectedLegacyEnrollmentsData); + + // Verify logInfo calls + expect(logInfo).toHaveBeenCalledTimes(expectedLogInfoCalls); + + // Verify side effects + expect(mockOnSuccess).toHaveBeenCalledTimes(1); + expect(mockAddToast).toHaveBeenCalledTimes(1); + expect(mockAddToast).toHaveBeenCalledWith('You have been unenrolled from the course.'); }); }); }); diff --git a/src/components/dashboard/main-content/course-enrollments/data/hooks.js b/src/components/dashboard/main-content/course-enrollments/data/hooks.js index acb33cb1b0..47eac0523b 100644 --- a/src/components/dashboard/main-content/course-enrollments/data/hooks.js +++ b/src/components/dashboard/main-content/course-enrollments/data/hooks.js @@ -9,7 +9,7 @@ import { sendEnterpriseTrackEventWithDelay } from '@edx/frontend-enterprise-util import _camelCase from 'lodash.camelcase'; import _cloneDeep from 'lodash.clonedeep'; -import { useLocation, useParams } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import * as service from './service'; import { COURSE_STATUSES, HAS_USER_DISMISSED_NEW_GROUP_ALERT } from './constants'; import { @@ -527,73 +527,63 @@ export function useCourseEnrollmentsBySection(courseEnrollmentsByStatus) { }; } -// TODO: There is opportunity to generalize this approach into a helper function -export function handleQueriesForUpdatedCourseEnrollmentStatus({ - queryClient, - enterpriseSlug, - enterpriseCustomer, - courseRunId, - newEnrollmentStatus, -}) { - // Transformation - const transformUpdatedEnrollment = (enrollment) => { - if (enrollment.courseRunId !== courseRunId) { - return enrollment; - } - return { - ...enrollment, - courseRunStatus: newEnrollmentStatus, - }; - }; - - const isBFFEnabled = isBFFEnabledForEnterpriseCustomer(enterpriseCustomer.uuid); - if (isBFFEnabled) { - // Determine which BFF queries need to be updated after unenrolling. - const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF( - { enterpriseSlug }, - ).queryKey; - const bffQueryKeysToUpdate = [dashboardBFFQueryKey]; - // Update the enterpriseCourseEnrollments data in the cache for each BFF query. - bffQueryKeysToUpdate.forEach((queryKey) => { - const existingBFFData = queryClient.getQueryData(queryKey); - if (!existingBFFData) { - logInfo(`Skipping optimistic cache update of ${queryKey} as no cached query data exists yet.`); - return; +/** + * - Provides a helper function to update the course enrollment status in the query cache. + * @param {Object} args + * @param {Object} args.enterpriseCustomer - Object containing enterprise customer data. + * @returns {Function} - Returns a function to update a course enrollment status in the query cache. The + * function accepts a courseRunId and newStatus (i.e., the new status for which to update the enrollment). + */ +export function useUpdateCourseEnrollmentStatus() { + const queryClient = useQueryClient(); + const { data: enterpriseCustomer } = useEnterpriseCustomer(); + return useCallback(({ courseRunId, newStatus }) => { + // Transformation to update the course enrollment status. + const transformUpdatedEnrollment = (enrollment) => { + if (enrollment.courseRunId !== courseRunId) { + return enrollment; } - const updatedBFFData = { - ...existingBFFData, - enterpriseCourseEnrollments: existingBFFData.enterpriseCourseEnrollments.map(transformUpdatedEnrollment), + return { + ...enrollment, + courseRunStatus: newStatus, }; - queryClient.setQueryData(queryKey, updatedBFFData); - }); - } else { + }; + + const isBFFEnabled = isBFFEnabledForEnterpriseCustomer(enterpriseCustomer.uuid); + if (isBFFEnabled) { + // Determine which BFF queries need to be updated after updating enrollment status. + const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF({ + enterpriseSlug: enterpriseCustomer.slug, + }).queryKey; + + const bffQueryKeysToUpdate = [dashboardBFFQueryKey]; + // Update the enterpriseCourseEnrollments data in the cache for each BFF query. + bffQueryKeysToUpdate.forEach((queryKey) => { + const existingBFFData = queryClient.getQueryData(queryKey); + if (!existingBFFData) { + logInfo(`Skipping optimistic cache update of ${JSON.stringify(queryKey)} as no cached query data exists yet.`); + return; + } + const updatedBFFData = { + ...existingBFFData, + enterpriseCourseEnrollments: existingBFFData.enterpriseCourseEnrollments.map(transformUpdatedEnrollment), + }; + queryClient.setQueryData(queryKey, updatedBFFData); + }); + } + // Update the legacy queryEnterpriseCourseEnrollments cache as well. const enterpriseCourseEnrollmentsQueryKey = queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid).queryKey; const existingCourseEnrollmentsData = queryClient.getQueryData(enterpriseCourseEnrollmentsQueryKey); if (!existingCourseEnrollmentsData) { - logInfo('Skipping optimistic cache update of {existingCourseEnrollmentsData} as no cached query data exists yet.'); + logInfo(`Skipping optimistic cache update of ${JSON.stringify(enterpriseCourseEnrollmentsQueryKey)} as no cached query data exists yet.`); return; } const updatedCourseEnrollmentsData = existingCourseEnrollmentsData.map(transformUpdatedEnrollment); queryClient.setQueryData(enterpriseCourseEnrollmentsQueryKey, updatedCourseEnrollmentsData); - } + }, [queryClient, enterpriseCustomer]); } -export const useUpdateCourseEnrollmentStatus = ({ enterpriseCustomer }) => { - const queryClient = useQueryClient(); - const params = useParams(); - - return useCallback(({ courseRunId, newStatus }) => { - handleQueriesForUpdatedCourseEnrollmentStatus({ - queryClient, - enterpriseSlug: params.enterpriseSlug, - enterpriseCustomer, - courseRunId, - newEnrollmentStatus: newStatus, - }); - }, [queryClient, params.enterpriseSlug, enterpriseCustomer]); -}; - /** * - Parses a list of redeemable policies and checks if learner has acknowledged the new group. * - Provides a helper function to handle adding the group uuid to local storage diff --git a/src/components/dashboard/main-content/course-enrollments/data/tests/hooks.test.jsx b/src/components/dashboard/main-content/course-enrollments/data/tests/hooks.test.jsx index 1f3770d866..92803d02cf 100644 --- a/src/components/dashboard/main-content/course-enrollments/data/tests/hooks.test.jsx +++ b/src/components/dashboard/main-content/course-enrollments/data/tests/hooks.test.jsx @@ -5,17 +5,17 @@ import { AppContext } from '@edx/frontend-platform/react'; import { sendEnterpriseTrackEventWithDelay } from '@edx/frontend-enterprise-utils'; import camelCase from 'lodash.camelcase'; import dayjs from 'dayjs'; -import { QueryClientProvider, useQueryClient } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { queryClient } from '../../../../../../utils/tests'; import { - handleQueriesForUpdatedCourseEnrollmentStatus, useContentAssignments, useCourseEnrollments, useCourseEnrollmentsBySection, useCourseUpgradeData, useGroupAssociationsAlert, + useUpdateCourseEnrollmentStatus, } from '../hooks'; import * as service from '../service'; import { COURSE_STATUSES, HAS_USER_DISMISSED_NEW_GROUP_ALERT } from '../constants'; @@ -90,10 +90,13 @@ const mockTransformedMockCourseEnrollment = transformCourseEnrollment(mockRawCou const mockEnterpriseCustomer = enterpriseCustomerFactory(); const mockAuthenticatedUser = authenticatedUserFactory(); -const mockEnterpriseCourseEnrollment = enterpriseCourseEnrollmentFactory(); +const mockEnterpriseCourseEnrollment = enterpriseCourseEnrollmentFactory({ + course_run_id: mockRawCourseEnrollment.courseRunId, +}); +const mockEnterpriseCourseEnrollments = [mockEnterpriseCourseEnrollment]; const mockBFFEnterpriseCourseEnrollments = { ...learnerDashboardBFFResponse, - enterpriseCourseEnrollments: [mockEnterpriseCourseEnrollment], + enterpriseCourseEnrollments: mockEnterpriseCourseEnrollments, }; const mockAppContextValue = { @@ -1064,136 +1067,156 @@ describe('useGroupAssociationsAlert', () => { }); }); -describe('handleQueriesForUpdatedCourseEnrollmentStatus', () => { +describe('useUpdateCourseEnrollmentStatus', () => { + let mockQueryClient; + const Wrapper = ({ + existingEnrollmentsQueryData = mockEnterpriseCourseEnrollments, + existingBFFDashboardQueryData = mockBFFEnterpriseCourseEnrollments, + children, + }) => { + mockQueryClient = queryClient(); + if (existingEnrollmentsQueryData) { + mockQueryClient.setQueryData( + queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, + existingEnrollmentsQueryData, + ); + } + if (existingBFFDashboardQueryData) { + mockQueryClient.setQueryData( + queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: mockEnterpriseCustomer.slug }).queryKey, + existingBFFDashboardQueryData, + ); + } + return ( + + {children} + + ); + }; + beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); + useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer }); }); + + afterEach(() => { + mockQueryClient = undefined; + }); + it.each([ // BFF enabled { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: true, + existingBFFDashboardQueryData: mockBFFEnterpriseCourseEnrollments, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: true, + existingBFFDashboardQueryData: mockBFFEnterpriseCourseEnrollments, + existingEnrollmentsQueryData: null, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: true, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: true, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: null, }, // BFF disabled { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: false, + existingBFFDashboardQueryData: mockBFFEnterpriseCourseEnrollments, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: mockBFFEnterpriseCourseEnrollments, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: false, + existingBFFDashboardQueryData: mockBFFEnterpriseCourseEnrollments, + existingEnrollmentsQueryData: null, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: mockEnterpriseCourseEnrollment, isBFFEnabled: false, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: mockEnterpriseCourseEnrollments, }, { - bffEnterpriseCourseEnrollmentsData: null, - enterpriseCourseEnrollmentsData: null, isBFFEnabled: false, + existingBFFDashboardQueryData: null, + existingEnrollmentsQueryData: null, }, - ])('updates the status, (%s)', ( - { - bffEnterpriseCourseEnrollmentsData, - enterpriseCourseEnrollmentsData, - isBFFEnabled, - }, - ) => { - // Define parameters - let mockQueryClient; + ])('updates the enrollment status (%s)', async ({ + isBFFEnabled, + existingBFFDashboardQueryData, + existingEnrollmentsQueryData, + }) => { isBFFEnabledForEnterpriseCustomer.mockReturnValue(isBFFEnabled); - const mockParams = { enterpriseSlug: 'test-enterprise-slug' }; const mockCourseRunId = mockEnterpriseCourseEnrollment.courseRunId; const newEnrollmentStatus = 'saved_for_later'; - // Create a mock hook to utilize queryClient - const useMockHook = () => { - mockQueryClient = useQueryClient(); - if (bffEnterpriseCourseEnrollmentsData) { - mockQueryClient.setQueryData( - queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: mockParams.enterpriseSlug }).queryKey, - bffEnterpriseCourseEnrollmentsData, - ); - } - if (enterpriseCourseEnrollmentsData) { - mockQueryClient.setQueryData( - queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, - [enterpriseCourseEnrollmentsData], - ); - } - return handleQueriesForUpdatedCourseEnrollmentStatus({ - queryClient: mockQueryClient, - enterpriseSlug: mockParams.enterpriseSlug, - enterpriseCustomer: mockEnterpriseCustomer, - courseRunId: mockCourseRunId, - newEnrollmentStatus, - }); - }; - // Validate initial courseRunStatus as `in_progress` - expect(mockEnterpriseCourseEnrollment.courseRunStatus).toEqual('in_progress'); + const originalEnrollmentStatus = mockEnterpriseCourseEnrollment.courseRunStatus; + expect(originalEnrollmentStatus).toEqual('in_progress'); - // Render hook - renderHook( - () => useMockHook(), - { wrapper }, + // Render the hook + const { result } = renderHook( + () => useUpdateCourseEnrollmentStatus(), + { + wrapper: ({ children }) => ( + + {children} + + ), + }, ); + expect(result.current).toBeDefined(); + expect(result.current).toBeInstanceOf(Function); + // Call the returned function to update the enrollment status + result.current({ + courseRunId: mockCourseRunId, + newStatus: newEnrollmentStatus, + }); - // Determine if course run status gets modified based on parameters - let updatedEnrollments; - let updatedMockedEnrollment; - if (isBFFEnabled) { - updatedEnrollments = mockQueryClient.getQueryData( - queryEnterpriseLearnerDashboardBFF({ enterpriseSlug: 'test-enterprise-slug' }).queryKey, - ); - updatedMockedEnrollment = updatedEnrollments?.enterpriseCourseEnrollments.find( - enrollment => enrollment.courseRunId === mockCourseRunId, + await waitFor(() => { + const dashboardBFFData = mockQueryClient.getQueryData( + queryEnterpriseLearnerDashboardBFF({ + enterpriseSlug: mockEnterpriseCustomer.slug, + }).queryKey, ); - if (bffEnterpriseCourseEnrollmentsData && !enterpriseCourseEnrollmentsData) { - expect(updatedMockedEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); - expect(logInfo).toHaveBeenCalledTimes(0); - } else if (!bffEnterpriseCourseEnrollmentsData) { - expect(updatedMockedEnrollment).toEqual(undefined); - expect(logInfo).toHaveBeenCalledTimes(1); - } else { - expect(updatedMockedEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); - expect(logInfo).toHaveBeenCalledTimes(0); + let expectedLogInfoCalls = 0; + if (isBFFEnabled) { + // Validate the updated enrollment status in BFF-related queries + const foundMockEnrollment = dashboardBFFData?.enterpriseCourseEnrollments.find( + (enrollment) => enrollment.courseRunId === mockCourseRunId, + ); + if (existingBFFDashboardQueryData) { + expect(foundMockEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); + } else { + expectedLogInfoCalls += 1; + expect(dashboardBFFData).toBeUndefined(); + } } - } - if (!isBFFEnabled) { - updatedEnrollments = mockQueryClient.getQueryData( + + // Always validate the updated enrollment status in non-BFF-related query + const enrollmentsData = mockQueryClient.getQueryData( queryEnterpriseCourseEnrollments(mockEnterpriseCustomer.uuid).queryKey, ); - updatedMockedEnrollment = updatedEnrollments?.find(enrollment => enrollment.courseRunId === mockCourseRunId); - if (enterpriseCourseEnrollmentsData && !bffEnterpriseCourseEnrollmentsData) { - expect(updatedMockedEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); - expect(logInfo).toHaveBeenCalledTimes(0); - } else if (!enterpriseCourseEnrollmentsData) { - expect(updatedMockedEnrollment).toEqual(undefined); - expect(logInfo).toHaveBeenCalledTimes(1); + const foundMockEnrollment = enrollmentsData?.find( + (enrollment) => enrollment.courseRunId === mockCourseRunId, + ); + if (existingEnrollmentsQueryData) { + expect(foundMockEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); } else { - expect(updatedMockedEnrollment.courseRunStatus).toEqual(newEnrollmentStatus); - expect(logInfo).toHaveBeenCalledTimes(0); + expectedLogInfoCalls += 1; + expect(enrollmentsData).toBeUndefined(); } - } + + // Verify logInfo calls + expect(logInfo).toHaveBeenCalledTimes(expectedLogInfoCalls); + }); }); }); diff --git a/src/components/executive-education-2u/UserEnrollmentForm.jsx b/src/components/executive-education-2u/UserEnrollmentForm.jsx index 63d8d5ef5c..8fedf55927 100644 --- a/src/components/executive-education-2u/UserEnrollmentForm.jsx +++ b/src/components/executive-education-2u/UserEnrollmentForm.jsx @@ -36,7 +36,6 @@ const UserEnrollmentForm = ({ className }) => { const navigate = useNavigate(); const config = getConfig(); const queryClient = useQueryClient(); - const params = useParams(); const intl = useIntl(); const { authenticatedUser: { userId, email: userEmail }, @@ -62,13 +61,15 @@ const UserEnrollmentForm = ({ className }) => { lmsUserId: userId, }).queryKey; const enterpriseCourseEnrollmentsQueryKey = queryEnterpriseCourseEnrollments(enterpriseCustomer.uuid).queryKey; + + // List of queries to invalidate after successfully enrolling in the course. const queriesToInvalidate = [canRedeemQueryKey, redeemablePoliciesQueryKey, enterpriseCourseEnrollmentsQueryKey]; if (isBFFEnabled) { // Determine which BFF queries need to be updated after successfully enrolling. - const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF( - { enterpriseSlug: params.enterpriseSlug }, - ).queryKey; + const dashboardBFFQueryKey = queryEnterpriseLearnerDashboardBFF({ + enterpriseSlug: enterpriseCustomer.slug, + }).queryKey; queriesToInvalidate.push(dashboardBFFQueryKey); } diff --git a/src/components/executive-education-2u/UserEnrollmentForm.test.jsx b/src/components/executive-education-2u/UserEnrollmentForm.test.jsx index 806525a717..950f2d82b3 100644 --- a/src/components/executive-education-2u/UserEnrollmentForm.test.jsx +++ b/src/components/executive-education-2u/UserEnrollmentForm.test.jsx @@ -1,6 +1,4 @@ -import { - act, screen, waitFor, -} from '@testing-library/react'; +import { act, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom/extend-expect'; import { AppContext } from '@edx/frontend-platform/react'; @@ -10,7 +8,7 @@ import { logError, logInfo } from '@edx/frontend-platform/logging'; import dayjs from 'dayjs'; import MockDate from 'mockdate'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { useParams } from 'react-router-dom'; import UserEnrollmentForm from './UserEnrollmentForm'; import { checkoutExecutiveEducation2U, toISOStringWithoutMilliseconds } from './data'; @@ -23,7 +21,7 @@ import { useEnterpriseCustomer, } from '../app/data'; import { authenticatedUserFactory, enterpriseCustomerFactory } from '../app/data/services/data/__factories__'; -import { renderWithRouter } from '../../utils/tests'; +import { queryClient, renderWithRouter } from '../../utils/tests'; import { useUserSubsidyApplicableToCourse } from '../course/data'; const termsLabelText = "I agree to GetSmarter's Terms and Conditions for Students"; @@ -97,8 +95,7 @@ const initialAppContextValue = { authenticatedUser: mockAuthenticatedUser, }; -const queryClient = new QueryClient(); - +let mockQueryClient; const UserEnrollmentFormWrapper = ({ appContextValue = initialAppContextValue, courseContextValue = { @@ -108,17 +105,20 @@ const UserEnrollmentFormWrapper = ({ setExternalFormSubmissionError: jest.fn(), formSubmissionError: {}, }, -}) => ( - - - - - - - - - -); +}) => { + mockQueryClient = queryClient(); + return ( + + + + + + + + + + ); +}; describe('UserEnrollmentForm', () => { beforeEach(() => {