diff --git a/src/course-home/data/apiHooks.test.tsx b/src/course-home/data/apiHooks.test.tsx index 3a157dd53d..801532eba2 100644 --- a/src/course-home/data/apiHooks.test.tsx +++ b/src/course-home/data/apiHooks.test.tsx @@ -1,5 +1,6 @@ import { act, renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Factory } from 'rosie'; import MockAdapter from 'axios-mock-adapter'; import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; @@ -7,8 +8,8 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '../../setupTest'; import { ToastProvider, useToast } from '../../generic/ToastContext'; import { - useOutlineTabData, useResetDeadlines, usePostEvent, useRequestCert, useDismissWelcomeMessage, - useSaveWeeklyLearningGoal, + useOutlineTabData, useProgressTabData, useResetDeadlines, usePostEvent, useRequestCert, + useDismissWelcomeMessage, useSaveWeeklyLearningGoal, } from './apiHooks'; const { loggingService } = initializeMockApp(); @@ -193,6 +194,68 @@ describe('course-home apiHooks', () => { }); }); + describe('useProgressTabData', () => { + const progressUrl = `${getConfig().LMS_BASE_URL}/api/course_home/progress/course-1`; + + it('transforms the server response', async () => { + axiosMock.onGet(progressUrl).reply(200, Factory.build('progressTabData')); + const { wrapper } = buildWrapper(); + const { result } = renderHook(() => useProgressTabData('course-1'), { wrapper }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(result.current.data.studioUrl).toEqual('http://studio.edx.org/settings/grading/course-v1:edX+Test+run'); + expect(result.current.data.gradesFeatureIsFullyLocked).toBe(false); + }); + + it('appends the targetUserId to the request URL', async () => { + axiosMock.onGet(`${progressUrl}/7/`).reply(200, Factory.build('progressTabData')); + const { wrapper } = buildWrapper(); + const { result } = renderHook(() => useProgressTabData('course-1', '7'), { wrapper }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(axiosMock.history.get[0].url).toEqual(`${progressUrl}/7/`); + }); + + it.each([401, 403])( + 'resolves to an empty object on a %s (access is handled via the metadata request)', + async (status) => { + axiosMock.onGet(progressUrl).reply(status, {}); + const { wrapper } = buildWrapper(); + const { result } = renderHook(() => useProgressTabData('course-1'), { wrapper }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(result.current.data).toEqual({}); + }, + ); + + it('redirects to the legacy progress page and resolves to an empty object on a 404', async () => { + // jsdom's location.replace is non-configurable, so we swap the whole location for the + // duration of this test (restored in finally so it can never bleed into another test). + const originalLocation = window.location; + const replace = jest.fn(); + Object.defineProperty(window, 'location', { configurable: true, value: { replace } }); + try { + axiosMock.onGet(progressUrl).reply(404, {}); + const { wrapper } = buildWrapper(); + const { result } = renderHook(() => useProgressTabData('course-1'), { wrapper }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(result.current.data).toEqual({}); + expect(replace).toHaveBeenCalledWith(`${getConfig().LMS_BASE_URL}/courses/course-1/progress`); + } finally { + Object.defineProperty(window, 'location', { configurable: true, value: originalLocation }); + } + }); + + it('surfaces the error on a non-handled failure', async () => { + axiosMock.onGet(progressUrl).reply(500); + const { wrapper } = buildWrapper(); + const { result } = renderHook(() => useProgressTabData('course-1'), { wrapper }); + + await waitFor(() => expect(result.current.isError).toBe(true)); + }); + }); + describe('useSaveWeeklyLearningGoal', () => { const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`; diff --git a/src/course-home/data/apiHooks.ts b/src/course-home/data/apiHooks.ts index ada7375297..1767ccae92 100644 --- a/src/course-home/data/apiHooks.ts +++ b/src/course-home/data/apiHooks.ts @@ -7,6 +7,7 @@ import { getCourseHomeCourseMetadata, getDatesTabData, getOutlineTabData, + getProgressTabData, postCourseDeadlines, postDismissWelcomeMessage, postRequestCert, @@ -74,6 +75,12 @@ export const useOutlineTabData = (courseId: string) => useQuery({ meta: { modelType: 'outline', courseId }, }); +export const useProgressTabData = (courseId: string, targetUserId?: string) => useQuery({ + queryKey: courseHomeQueryKeys.progressTab(courseId, targetUserId), + queryFn: () => getProgressTabData(courseId, targetUserId), + meta: { modelType: 'progress', courseId }, +}); + export const useRequestCert = () => useMutation({ mutationFn: ({ courseId }: { courseId: string }) => postRequestCert(courseId), onError: (error) => logError(error), diff --git a/src/course-home/data/index.js b/src/course-home/data/index.js index 14b7826f01..f427a06bff 100644 --- a/src/course-home/data/index.js +++ b/src/course-home/data/index.js @@ -1,6 +1,3 @@ -export { - fetchProgressTab, - deprecatedSaveCourseGoal, -} from './thunks'; +export { deprecatedSaveCourseGoal } from './thunks'; export { reducer } from './slice'; diff --git a/src/course-home/data/queryKeys.ts b/src/course-home/data/queryKeys.ts index 15dfcd794e..c671efba99 100644 --- a/src/course-home/data/queryKeys.ts +++ b/src/course-home/data/queryKeys.ts @@ -5,4 +5,5 @@ export const courseHomeQueryKeys = { metadata: (courseId: string) => [...courseHomeQueryKeys.all, 'metadata', courseId] as const, datesTab: (courseId: string) => [...courseHomeQueryKeys.all, 'datesTab', courseId] as const, outlineTab: (courseId: string) => [...courseHomeQueryKeys.all, 'outlineTab', courseId] as const, + progressTab: (courseId: string, targetUserId?: string) => [...courseHomeQueryKeys.all, 'progressTab', courseId, targetUserId] as const, }; diff --git a/src/course-home/data/redux.test.js b/src/course-home/data/redux.test.js index 453faf9f02..e434a6fd31 100644 --- a/src/course-home/data/redux.test.js +++ b/src/course-home/data/redux.test.js @@ -42,61 +42,6 @@ describe('Data layer integration tests', () => { store = initializeStore(); }); - describe('Test fetchProgressTab', () => { - const progressBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/progress`; - - it('Should result in fetch failure if error occurs', async () => { - axiosMock.onGet(courseMetadataUrl).networkError(); - axiosMock.onGet(`${progressBaseUrl}/${courseId}`).networkError(); - - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - - expect(loggingService.logError).toHaveBeenCalled(); - expect(store.getState().courseHome.courseStatus).toEqual('failed'); - }); - - it('Should fetch, normalize, and save metadata', async () => { - const progressTabData = Factory.build('progressTabData', { courseId }); - - const progressUrl = `${progressBaseUrl}/${courseId}`; - - axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); - axiosMock.onGet(progressUrl).reply(200, progressTabData); - - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - - const state = store.getState(); - expect(state.courseHome.courseStatus).toEqual('loaded'); - }); - - it('Should handle the url including a targetUserId', async () => { - const progressTabData = Factory.build('progressTabData', { courseId }); - const targetUserId = 2; - const progressUrl = `${progressBaseUrl}/${courseId}/${targetUserId}/`; - - axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); - axiosMock.onGet(progressUrl).reply(200, progressTabData); - - await executeThunk(thunks.fetchProgressTab(courseId, 2), store.dispatch); - - const state = store.getState(); - expect(state.courseHome.targetUserId).toEqual(2); - }); - - it.each([401, 403, 404])( - 'should result in fetch denied for expected errors and failed for all others', - async (errorStatus) => { - const progressUrl = `${progressBaseUrl}/${courseId}`; - axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata); - axiosMock.onGet(progressUrl).reply(errorStatus, {}); - - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - - expect(store.getState().courseHome.courseStatus).toEqual('denied'); - }, - ); - }); - describe('Test saveCourseGoal', () => { it('Should save course goal', async () => { const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`; @@ -282,4 +227,45 @@ describe('Data layer integration tests', () => { expect(loggingService.logError).toHaveBeenCalled(); }); }); + + describe('Test fetchTab', () => { + const liveUrl = `${getConfig().LMS_BASE_URL}/api/course_live/iframe/${courseId}/`; + + it('Should fetch metadata + tab data and mark the tab loaded', async () => { + axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); + axiosMock.onGet(liveUrl).reply(200, { iframe: 'https://example.com/live' }); + + await executeThunk(thunks.fetchLiveTab(courseId), store.dispatch); + + expect(store.getState().courseHome.courseStatus).toEqual('loaded'); + }); + + it('Should result in denied when the learner lacks course access', async () => { + axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata); + axiosMock.onGet(liveUrl).reply(200, {}); + + await executeThunk(thunks.fetchLiveTab(courseId), store.dispatch); + + expect(store.getState().courseHome.courseStatus).toEqual('denied'); + }); + + it('Should result in failure when the metadata request errors', async () => { + axiosMock.onGet(courseMetadataUrl).networkError(); + axiosMock.onGet(liveUrl).reply(200, {}); + + await executeThunk(thunks.fetchLiveTab(courseId), store.dispatch); + + expect(loggingService.logError).toHaveBeenCalled(); + expect(store.getState().courseHome.courseStatus).toEqual('failed'); + }); + + it('Should result in failure when the tab data request errors', async () => { + axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); + axiosMock.onGet(liveUrl).reply(500); + + await executeThunk(thunks.fetchLiveTab(courseId), store.dispatch); + + expect(store.getState().courseHome.courseStatus).toEqual('failed'); + }); + }); }); diff --git a/src/course-home/data/thunks.js b/src/course-home/data/thunks.js index dc1a37c446..64849b7140 100644 --- a/src/course-home/data/thunks.js +++ b/src/course-home/data/thunks.js @@ -2,7 +2,6 @@ import { logError } from '@edx/frontend-platform/logging'; import { getCourseHomeCourseMetadata, getExamsData, - getProgressTabData, deprecatedPostCourseGoals, getLiveTabIframe, } from './api'; @@ -81,10 +80,6 @@ export function fetchTab(courseId, tab, getTabData, targetUserId) { }; } -export function fetchProgressTab(courseId, targetUserId) { - return fetchTab(courseId, 'progress', getProgressTabData, parseInt(targetUserId, 10) || targetUserId); -} - export function fetchLiveTab(courseId) { return fetchTab(courseId, 'live', getLiveTabIframe); } diff --git a/src/course-home/progress-tab/ProgressHeader.jsx b/src/course-home/progress-tab/ProgressHeader.jsx index 6223b1fc4d..243908d136 100644 --- a/src/course-home/progress-tab/ProgressHeader.jsx +++ b/src/course-home/progress-tab/ProgressHeader.jsx @@ -1,22 +1,19 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Button } from '@openedx/paragon'; -import { useSelector } from 'react-redux'; - -import { useModel } from '../../generic/model-store'; +import { useParams } from 'react-router-dom'; +import { useProgressData } from './hooks'; import messages from './messages'; const ProgressHeader = () => { const intl = useIntl(); - const { - courseId, - targetUserId, - } = useSelector(state => state.courseHome); + const { targetUserId: targetUserIdParam } = useParams(); + const targetUserId = parseInt(targetUserIdParam, 10); const { administrator, userId } = getAuthenticatedUser(); - const { studioUrl, username } = useModel('progress', courseId); + const { studioUrl, username } = useProgressData(); const viewingOtherStudentsProgressPage = (targetUserId && targetUserId !== userId); diff --git a/src/course-home/progress-tab/ProgressTab.jsx b/src/course-home/progress-tab/ProgressTab.jsx index 32506930bf..7cfc6961e5 100644 --- a/src/course-home/progress-tab/ProgressTab.jsx +++ b/src/course-home/progress-tab/ProgressTab.jsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { useWindowSize } from '@openedx/paragon'; -import { useContextId } from '../../data/hooks'; -import { useModel } from '../../generic/model-store'; +import { useParams } from 'react-router-dom'; + import ProgressTabCertificateStatusSidePanelSlot from '../../plugin-slots/ProgressTabCertificateStatusSidePanelSlot'; import CourseCompletion from './course-completion/CourseCompletion'; @@ -11,11 +11,13 @@ import ProgressTabCertificateStatusMainBodySlot from '../../plugin-slots/Progres import ProgressTabCourseGradeSlot from '../../plugin-slots/ProgressTabCourseGradeSlot'; import ProgressTabGradeBreakdownSlot from '../../plugin-slots/ProgressTabGradeBreakdownSlot'; import ProgressTabRelatedLinksSlot from '../../plugin-slots/ProgressTabRelatedLinksSlot'; -import { useGetExamsData } from './hooks'; +import { useGetExamsData, useProgressData } from './hooks'; +import { useCourseHomeMeta, useProgressTabData } from '../data/apiHooks'; +import { TabWithTimer } from '../../tab-page'; -const ProgressTab = () => { - const courseId = useContextId(); - const { disableProgressGraph, sectionScores } = useModel('progress', courseId); +const ProgressTabContent = () => { + const { courseId } = useParams(); + const { disableProgressGraph, sectionScores } = useProgressData(); const sequenceIds = useMemo(() => ( sectionScores.flatMap((section) => (section.subsections)).map((subsection) => subsection.blockKey) @@ -53,4 +55,20 @@ const ProgressTab = () => { ); }; +const ProgressTab = () => { + const { courseId, targetUserId } = useParams(); + const metadataQuery = useCourseHomeMeta(courseId); + const tabDataQuery = useProgressTabData(courseId, targetUserId); + + return ( + + + + ); +}; + export default ProgressTab; diff --git a/src/course-home/progress-tab/ProgressTab.test.jsx b/src/course-home/progress-tab/ProgressTab.test.jsx index 2abfc286f1..d75a0c5a2c 100644 --- a/src/course-home/progress-tab/ProgressTab.test.jsx +++ b/src/course-home/progress-tab/ProgressTab.test.jsx @@ -1,20 +1,23 @@ import React from 'react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { Factory } from 'rosie'; import { getConfig, setConfig } from '@edx/frontend-platform'; +import { AppProvider } from '@edx/frontend-platform/react'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { breakpoints } from '@openedx/paragon'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { render } from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; import { - fireEvent, initializeMockApp, logUnhandledRequests, render, screen, act, waitFor, + createTestQueryClient, fireEvent, initializeMockApp, logUnhandledRequests, screen, within, act, waitFor, } from '../../setupTest'; -import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils'; -import * as thunks from '../data/thunks'; +import { appendBrowserTimezoneToUrl } from '../../utils'; import initializeStore from '../../store'; import ProgressTab from './ProgressTab'; -import LoadedTabPage from '../../tab-page/LoadedTabPage'; -import { TourProvider } from '../../product-tours/TourContext'; +import { UserMessagesProvider } from '../../generic/user-messages'; +import { ToastProvider } from '../../generic/ToastContext'; import messages from './grades/messages'; const mockCoursewareSearchParams = jest.fn(); @@ -64,9 +67,25 @@ describe('Progress Tab', () => { axiosMock.onGet(progressUrl).reply(200, progressTabData); } - async function fetchAndRender() { - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - await act(async () => render(, { store })); + async function fetchAndRender(initialEntry = `/course/${courseId}/progress`) { + const queryClient = createTestQueryClient(store); + await act(async () => render( + + + + + + + } /> + + + + + + , + )); + await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument()); + return queryClient; } beforeEach(async () => { @@ -96,7 +115,8 @@ describe('Progress Tab', () => { await fetchAndRender(); sendTrackEvent.mockClear(); - const datesTabLink = screen.getByRole('link', { name: 'Dates' }); + const relatedLinks = within(screen.getByRole('heading', { name: 'Related links' }).closest('section')); + const datesTabLink = relatedLinks.getByRole('link', { name: 'Dates' }); fireEvent.click(datesTabLink); expect(sendTrackEvent).toHaveBeenCalledTimes(1); @@ -320,7 +340,7 @@ describe('Progress Tab', () => { await fetchAndRender(); expect(screen.getByText('locked feature')).toBeInTheDocument(); expect(screen.getByText('Unlock to view grades and work towards a certificate.')).toBeInTheDocument(); - expect(screen.getAllByRole('link', 'Unlock now')).toHaveLength(3); + expect(screen.getAllByRole('link', { name: 'Upgrade now' })).toHaveLength(1); }); it('sends events on click of upgrade button in locked content header (CourseGradeHeader)', async () => { @@ -364,7 +384,7 @@ describe('Progress Tab', () => { expect(screen.getByText('locked feature')).toBeInTheDocument(); expect(screen.getByText('Unlock to view grades and work towards a certificate.')).toBeInTheDocument(); - const upgradeButton = screen.getAllByRole('link', 'Unlock now')[0]; + const upgradeButton = screen.getAllByRole('link', { name: 'Upgrade now' })[0]; fireEvent.click(upgradeButton); expect(sendTrackEvent).toHaveBeenCalledTimes(2); @@ -1435,8 +1455,7 @@ describe('Progress Tab', () => { masquerading_expired_course: true, }, }); - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - await act(async () => render(..., { store })); + await fetchAndRender(); expect(screen.getByTestId('instructor-toolbar')).toBeInTheDocument(); expect(screen.getByText('This learner no longer has access to this course. Their access expired on', { exact: false })).toBeInTheDocument(); expect(screen.getByText('1/1/2020', { exact: false })).toBeInTheDocument(); @@ -1449,8 +1468,7 @@ describe('Progress Tab', () => { masquerading_expired_course: false, }, }); - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - await act(async () => render(..., { store })); + await fetchAndRender(); expect(screen.queryByText('This learner no longer has access to this course. Their access expired on', { exact: false })).not.toBeInTheDocument(); expect(screen.queryByText('1/1/2020', { exact: false })).not.toBeInTheDocument(); }); @@ -1464,8 +1482,7 @@ describe('Progress Tab', () => { is_staff: false, start: '2999-01-01T00:00:00Z', }); - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - await act(async () => render(..., { store })); + await fetchAndRender(); expect(screen.getByTestId('instructor-toolbar')).toBeInTheDocument(); expect(screen.getByText('This learner does not yet have access to this course. The course starts on', { exact: false })).toBeInTheDocument(); expect(screen.getByText('1/1/2999', { exact: false })).toBeInTheDocument(); @@ -1477,8 +1494,7 @@ describe('Progress Tab', () => { is_staff: true, start: '2999-01-01T00:00:00Z', }); - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); - await act(async () => render(..., { store })); + await fetchAndRender(); expect(screen.queryByText('This learner does not yet have access to this course. The course starts on', { exact: false })).not.toBeInTheDocument(); expect(screen.queryByText('1/1/2999', { exact: false })).not.toBeInTheDocument(); }); @@ -1489,8 +1505,7 @@ describe('Progress Tab', () => { setMetadata({ is_enrolled: true }); setTabData({ username: 'otherstudent' }); - await executeThunk(thunks.fetchProgressTab(courseId, 10), store.dispatch); - await act(async () => render(, { store })); + await fetchAndRender(`/course/${courseId}/progress/10/`); expect(screen.getByText('Course progress for otherstudent')).toBeInTheDocument(); }); @@ -1631,7 +1646,7 @@ describe('Progress Tab', () => { section_scores: [mockSectionScores[0]], // Only first section }); - await fetchAndRender(); + const queryClient = await fetchAndRender(); // Verify initial API calls (2 subsections in first section) expect(axiosMock.history.get.filter(req => req.url.includes('/api/v1/student/exam/attempt/'))).toHaveLength(2); @@ -1639,12 +1654,14 @@ describe('Progress Tab', () => { // Clear axios history to track new calls axiosMock.resetHistory(); - // Update with full section scores and re-render + // Update with full section scores and refetch the tab query setTabData({ section_scores: mockSectionScores }); - await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch); + await act(async () => { await queryClient.invalidateQueries(); }); // Verify additional API calls for all subsections - expect(axiosMock.history.get.filter(req => req.url.includes('/api/v1/student/exam/attempt/'))).toHaveLength(3); + await waitFor(() => expect( + axiosMock.history.get.filter(req => req.url.includes('/api/v1/student/exam/attempt/')), + ).toHaveLength(3)); }); it('should handle exam API errors gracefully without breaking ProgressTab', async () => { diff --git a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx index e7dd2c21f9..31109250da 100644 --- a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx +++ b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx @@ -5,17 +5,18 @@ import { FormattedDate, FormattedMessage, useIntl } from '@edx/frontend-platform import { Button, Card } from '@openedx/paragon'; import { getConfig } from '@edx/frontend-platform'; -import { useContextId } from '../../../data/hooks'; +import { useParams } from 'react-router-dom'; import { useModel } from '../../../generic/model-store'; import { COURSE_EXIT_MODES, getCourseExitMode } from '../../../courseware/course/course-exit/utils'; import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links'; import { useRequestCert } from '../../data/apiHooks'; +import { useProgressData } from '../hooks'; import messages from './messages'; import ProgressCertificateStatusSlot from '../../../plugin-slots/ProgressCertificateStatusSlot'; const CertificateStatus = () => { const intl = useIntl(); - const courseId = useContextId(); + const { courseId } = useParams(); const { entranceExamData, @@ -39,7 +40,7 @@ const CertificateStatus = () => { userHasPassingGrade, verificationData, verifiedMode, - } = useModel('progress', courseId); + } = useProgressData(); const { certificateAvailableDate, } = certificateData || {}; diff --git a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx index a74c3a0a5c..bc47deb50f 100644 --- a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx +++ b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx @@ -1,6 +1,5 @@ import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; -import { useContextId } from '../../../data/hooks'; -import { useModel } from '../../../generic/model-store'; +import { useProgressData } from '../hooks'; import CompleteDonutSegment from './CompleteDonutSegment'; import IncompleteDonutSegment from './IncompleteDonutSegment'; @@ -9,7 +8,6 @@ import messages from './messages'; const CompletionDonutChart = () => { const intl = useIntl(); - const courseId = useContextId(); const { completionSummary: { @@ -17,7 +15,7 @@ const CompletionDonutChart = () => { incompleteCount, lockedCount, }, - } = useModel('progress', courseId); + } = useProgressData(); const numTotalUnits = completeCount + incompleteCount + lockedCount; const completePercentage = completeCount ? Number(((completeCount / numTotalUnits) * 100).toFixed(0)) : 0; diff --git a/src/course-home/progress-tab/credit-information/CreditInformation.jsx b/src/course-home/progress-tab/credit-information/CreditInformation.jsx index c12c4de2ec..d8803604a8 100644 --- a/src/course-home/progress-tab/credit-information/CreditInformation.jsx +++ b/src/course-home/progress-tab/credit-information/CreditInformation.jsx @@ -2,20 +2,18 @@ import { getConfig } from '@edx/frontend-platform'; import { useIntl } from '@edx/frontend-platform/i18n'; import { CheckCircle, WarningFilled, WatchFilled } from '@openedx/paragon/icons'; import { Hyperlink, Icon } from '@openedx/paragon'; -import { useContextId } from '../../../data/hooks'; -import { useModel } from '../../../generic/model-store'; +import { useProgressData } from '../hooks'; import { DashboardLink } from '../../../shared/links'; import messages from './messages'; const CreditInformation = () => { const intl = useIntl(); - const courseId = useContextId(); const { creditCourseRequirements, - } = useModel('progress', courseId); + } = useProgressData(); if (!creditCourseRequirements) { return null; } diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx index d69c6eee15..3cc94f790f 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx @@ -1,7 +1,6 @@ import { useIntl } from '@edx/frontend-platform/i18n'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import CourseGradeFooter from './CourseGradeFooter'; import CourseGradeHeader from './CourseGradeHeader'; @@ -12,7 +11,6 @@ import messages from '../messages'; const CourseGrade = () => { const intl = useIntl(); - const courseId = useContextId(); const { creditCourseRequirements, @@ -21,7 +19,7 @@ const CourseGrade = () => { gradingPolicy: { gradeRange, }, - } = useModel('progress', courseId); + } = useProgressData(); const passingGrade = Number((Math.min(...Object.values(gradeRange)) * 100).toFixed(0)); diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx index 6cada4cbb4..feb7c881b9 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx @@ -3,8 +3,7 @@ import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import { CheckCircle, WarningFilled } from '@openedx/paragon/icons'; import { breakpoints, Icon, useWindowSize } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import GradeRangeTooltip from './GradeRangeTooltip'; import messages from '../messages'; @@ -45,13 +44,12 @@ const NoticeRow = ({ const CourseGradeFooter = ({ passingGrade }) => { const intl = useIntl(); - const courseId = useContextId(); const { assignmentTypeGradeSummary, courseGrade: { isPassing, letterGrade }, gradingPolicy: { gradeRange }, - } = useModel('progress', courseId); + } = useProgressData(); const latestDueDate = getLatestDueDateInFuture(assignmentTypeGradeSummary); const wideScreen = useWindowSize().width >= breakpoints.medium.minWidth; diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx index f6e036de0a..6b198b7690 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx @@ -3,21 +3,22 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Locked } from '@openedx/paragon/icons'; import { Button, Icon } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; +import { useParams } from 'react-router-dom'; import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; const CourseGradeHeader = () => { const intl = useIntl(); - const courseId = useContextId(); + const { courseId } = useParams(); const { org, } = useModel('courseHomeMeta', courseId); const { verifiedMode, gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); const eventProperties = { org_key: org, diff --git a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx index 8e1c6b2985..117e89ac4c 100644 --- a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx @@ -2,15 +2,13 @@ import PropTypes from 'prop-types'; import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { OverlayTrigger, Popover } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; const CurrentGradeTooltip = ({ tooltipClassName }) => { const intl = useIntl(); - const courseId = useContextId(); const { assignmentTypeGradeSummary, @@ -18,7 +16,7 @@ const CurrentGradeTooltip = ({ tooltipClassName }) => { isPassing, percent, }, - } = useModel('progress', courseId); + } = useProgressData(); const currentGrade = Number((percent * 100).toFixed(0)); diff --git a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx index bb45f366a2..1a8bbc4d40 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx @@ -1,8 +1,7 @@ import PropTypes from 'prop-types'; import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import CurrentGradeTooltip from './CurrentGradeTooltip'; import PassingGradeTooltip from './PassingGradeTooltip'; @@ -10,7 +9,6 @@ import messages from '../messages'; const GradeBar = ({ passingGrade }) => { const intl = useIntl(); - const courseId = useContextId(); const { courseGrade: { @@ -18,7 +16,7 @@ const GradeBar = ({ passingGrade }) => { percent, }, gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); const currentGrade = Number((percent * 100).toFixed(0)); diff --git a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx index 9123b2cdd4..31fd6e4f58 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx @@ -6,21 +6,19 @@ import { InfoOutline } from '@openedx/paragon/icons'; import { Icon, IconButton, OverlayTrigger, Popover, } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; const GradeRangeTooltip = ({ iconButtonClassName, passingGrade }) => { const intl = useIntl(); - const courseId = useContextId(); const { gradesFeatureIsFullyLocked, gradingPolicy: { gradeRange, }, - } = useModel('progress', courseId); + } = useProgressData(); const [showTooltip, setShowTooltip] = useState(false); diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx index 6b61869888..8af43482f3 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx @@ -3,8 +3,9 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Locked } from '@openedx/paragon/icons'; import { Icon, Hyperlink } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; +import { useParams } from 'react-router-dom'; import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import { showUngradedAssignments } from '../../utils'; import DetailedGradesTable from './DetailedGradesTable'; @@ -14,7 +15,7 @@ import messages from '../messages'; const DetailedGrades = () => { const intl = useIntl(); const { administrator } = getAuthenticatedUser(); - const courseId = useContextId(); + const { courseId } = useParams(); const { org, tabs, @@ -23,7 +24,7 @@ const DetailedGrades = () => { gradesFeatureIsFullyLocked, gradesFeatureIsPartiallyLocked, sectionScores, - } = useModel('progress', courseId); + } = useProgressData(); const hasSectionScores = sectionScores.length > 0; const emptyTableMsg = showUngradedAssignments() diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx index 723aeae49f..66f9878dc1 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx @@ -1,19 +1,17 @@ import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; import SubsectionTitleCell from './SubsectionTitleCell'; import { showUngradedAssignments } from '../../utils'; const DetailedGradesTable = () => { const intl = useIntl(); - const courseId = useContextId(); const { sectionScores, - } = useModel('progress', courseId); + } = useProgressData(); const isLocaleRtl = isRtl(getLocale()); return ( diff --git a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx index 3f856082d8..594beb25dd 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx @@ -10,21 +10,22 @@ import { Info, Locked, } from '@openedx/paragon/icons'; -import { useContextId } from '../../../../data/hooks'; +import { useParams } from 'react-router-dom'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import ProblemScoreDrawer from './ProblemScoreDrawer'; const SubsectionTitleCell = ({ subsection }) => { const intl = useIntl(); - const courseId = useContextId(); + const { courseId } = useParams(); const { org, } = useModel('courseHomeMeta', courseId); const { gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); const { blockKey, diff --git a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx index b84d07e27b..3ba0c7044c 100644 --- a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx @@ -2,19 +2,17 @@ import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Locked } from '@openedx/paragon/icons'; import { Icon } from '@openedx/paragon'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; const AssignmentTypeCell = ({ assignmentType, footnoteMarker, footnoteId, locked, }) => { const intl = useIntl(); - const courseId = useContextId(); const { gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); const lockedIcon = locked ? : ''; diff --git a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx index 199fbb42f4..7549309bc7 100644 --- a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx @@ -1,17 +1,15 @@ import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useContextId } from '../../../../data/hooks'; import messages from '../messages'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; const DroppableAssignmentFootnote = ({ footnotes }) => { const intl = useIntl(); - const courseId = useContextId(); const { gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); return ( <> {intl.formatMessage(messages.footnotesTitle)} diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx index 6066997a9f..e966aa10a9 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx @@ -1,17 +1,14 @@ import React, { useState } from 'react'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import GradeSummaryHeader from './GradeSummaryHeader'; import GradeSummaryTable from './GradeSummaryTable'; const GradeSummary = () => { - const courseId = useContextId(); - const { assignmentTypeGradeSummary, - } = useModel('progress', courseId); + } = useProgressData(); const [allOfSomeAssignmentTypeIsLocked, setAllOfSomeAssignmentTypeIsLocked] = useState(false); diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx index df1ff65836..6f038c391c 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx @@ -8,18 +8,16 @@ import { Tooltip, } from '@openedx/paragon'; import { InfoOutline, Locked } from '@openedx/paragon/icons'; -import { useContextId } from '../../../../data/hooks'; import messages from '../messages'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => { const intl = useIntl(); - const courseId = useContextId(); const { verifiedMode, gradesFeatureIsFullyLocked, - } = useModel('progress', courseId); + } = useProgressData(); return ( diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx index 44129521e1..cda32d2b1d 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx @@ -3,8 +3,7 @@ import PropTypes from 'prop-types'; import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; import { Lock } from '@openedx/paragon/icons'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import AssignmentTypeCell from './AssignmentTypeCell'; import DroppableAssignmentFootnote from './DroppableAssignmentFootnote'; @@ -14,13 +13,12 @@ import messages from '../messages'; const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => { const intl = useIntl(); - const courseId = useContextId(); const { assignmentTypeGradeSummary, gradesFeatureIsFullyLocked, sectionScores, - } = useModel('progress', courseId); + } = useProgressData(); const footnotes = []; diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx index e8655e6eb7..903c6ebf3d 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx @@ -7,14 +7,12 @@ import { Tooltip, } from '@openedx/paragon'; import { InfoOutline } from '@openedx/paragon/icons'; -import { useContextId } from '../../../../data/hooks'; -import { useModel } from '../../../../generic/model-store'; +import { useProgressData } from '../../hooks'; import messages from '../messages'; const GradeSummaryTableFooter = () => { const intl = useIntl(); - const courseId = useContextId(); const { courseGrade: { @@ -22,7 +20,7 @@ const GradeSummaryTableFooter = () => { percent, }, finalGrades, - } = useModel('progress', courseId); + } = useProgressData(); const getGradePercent = (grade) => { const percentage = grade * 100; diff --git a/src/course-home/progress-tab/hooks.jsx b/src/course-home/progress-tab/hooks.jsx index d1b707bc80..799d4af4a0 100644 --- a/src/course-home/progress-tab/hooks.jsx +++ b/src/course-home/progress-tab/hooks.jsx @@ -1,7 +1,9 @@ import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; import { fetchExamAttemptsData } from '../data/thunks'; +import { useProgressTabData } from '../data/apiHooks'; export function useGetExamsData(courseId, sequenceIds) { const dispatch = useDispatch(); @@ -10,3 +12,8 @@ export function useGetExamsData(courseId, sequenceIds) { dispatch(fetchExamAttemptsData(courseId, sequenceIds)); }, [dispatch, courseId, sequenceIds]); } + +export function useProgressData() { + const { courseId, targetUserId } = useParams(); + return useProgressTabData(courseId, targetUserId).data; +} diff --git a/src/course-home/progress-tab/related-links/RelatedLinks.jsx b/src/course-home/progress-tab/related-links/RelatedLinks.jsx index cf0c27db4a..f2ff1b34f8 100644 --- a/src/course-home/progress-tab/related-links/RelatedLinks.jsx +++ b/src/course-home/progress-tab/related-links/RelatedLinks.jsx @@ -2,14 +2,14 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Hyperlink } from '@openedx/paragon'; -import { useContextId } from '../../../data/hooks'; +import { useParams } from 'react-router-dom'; import messages from './messages'; import { useModel } from '../../../generic/model-store'; const RelatedLinks = () => { const intl = useIntl(); - const courseId = useContextId(); + const { courseId } = useParams(); const { org, tabs, diff --git a/src/index.jsx b/src/index.jsx index 5b7d6ba358..0f33148bb6 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -26,7 +26,6 @@ import GoalUnsubscribe from './course-home/goal-unsubscribe'; import ProgressTab from './course-home/progress-tab/ProgressTab'; import { TabContainer } from './tab-page'; -import { fetchProgressTab } from './course-home/data'; import { fetchCourse } from './courseware/data'; import { store } from './store'; import { createQueryClient } from './queryClient'; @@ -113,14 +112,7 @@ subscribe(APP_READY, () => { path={route} element={( - - - + )} /> diff --git a/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx b/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx index 54fccfaffc..7be22fb928 100644 --- a/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx +++ b/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx @@ -1,13 +1,11 @@ -import { useModel } from '@src/generic/model-store'; import { PluginSlot } from '@openedx/frontend-plugin-framework'; import React from 'react'; import DetailedGrades from '../../course-home/progress-tab/grades/detailed-grades/DetailedGrades'; import GradeSummary from '../../course-home/progress-tab/grades/grade-summary/GradeSummary'; -import { useContextId } from '../../data/hooks'; +import { useProgressData } from '../../course-home/progress-tab/hooks'; const ProgressTabGradeBreakdownSlot = () => { - const courseId = useContextId(); - const { gradesFeatureIsFullyLocked } = useModel('progress', courseId); + const { gradesFeatureIsFullyLocked } = useProgressData(); const applyLockedOverlay = gradesFeatureIsFullyLocked ? 'locked-overlay' : ''; return ( { fetch, slice, tab, - isProgressTab, } = props; - const { courseId: courseIdFromUrl, targetUserId } = useParams(); + const { courseId: courseIdFromUrl } = useParams(); const dispatch = useDispatch(); useEffect(() => { // The courseId from the URL is the course we WANT to load. - if (isProgressTab) { - dispatch(fetch(courseIdFromUrl, targetUserId)); - } else { - dispatch(fetch(courseIdFromUrl)); - } + dispatch(fetch(courseIdFromUrl)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [courseIdFromUrl, targetUserId]); + }, [courseIdFromUrl]); // The courseId from the store is the course we HAVE loaded. If the URL changes, // we don't want the application to adjust to it until it has actually loaded the new data. @@ -50,11 +45,6 @@ TabContainer.propTypes = { fetch: PropTypes.func.isRequired, slice: PropTypes.string.isRequired, tab: PropTypes.string.isRequired, - isProgressTab: PropTypes.bool, -}; - -TabContainer.defaultProps = { - isProgressTab: false, }; export default TabContainer; diff --git a/src/tab-page/TabContainer.test.jsx b/src/tab-page/TabContainer.test.jsx index fc0d5f39e4..3eb95f6ad0 100644 --- a/src/tab-page/TabContainer.test.jsx +++ b/src/tab-page/TabContainer.test.jsx @@ -51,32 +51,4 @@ describe('Tab Container', () => { expect(mockDispatch).toHaveBeenCalledWith(courseId); expect(screen.getByTestId('TabPage')).toBeInTheDocument(); }); - - it('Should handle passing in a targetUserId', () => { - const targetUserId = '1'; - - render( - - - - children={[]} - - )} - /> - - , - ); - - expect(mockFetch).toHaveBeenCalledTimes(1); - expect(mockFetch).toHaveBeenCalledWith(courseId, targetUserId); - expect(screen.getByTestId('TabPage')).toBeInTheDocument(); - }); });