Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/course-home/data/apiHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

import { initializeMockApp } from '../../setupTest';
import { ToastProvider, useToast } from '../../generic/ToastContext';
import { useResetDeadlines, usePostEvent } from './apiHooks';
import { useResetDeadlines, usePostEvent, useRequestCert } from './apiHooks';

const { loggingService } = initializeMockApp();

Expand Down Expand Up @@ -116,4 +116,30 @@ describe('course-home apiHooks', () => {
await waitFor(() => expect(loggingService.logError).toHaveBeenCalled());
});
});

describe('useRequestCert', () => {
const certUrl = `${getConfig().LMS_BASE_URL}/courses/course-1/generate_user_cert`;

it('POSTs to the request-cert url', async () => {
axiosMock.onPost(certUrl).reply(200);
const { wrapper } = buildWrapper();
const { result } = renderHook(() => useRequestCert(), { wrapper });

await act(async () => { await result.current.mutateAsync({ courseId: 'course-1' }); });

expect(axiosMock.history.post[0].url).toEqual(certUrl);
});

it('logs the error when the POST fails', async () => {
axiosMock.onPost(certUrl).reply(500);
const { wrapper } = buildWrapper();
const { result } = renderHook(() => useRequestCert(), { wrapper });

await act(async () => {
await result.current.mutateAsync({ courseId: 'course-1' }).catch(() => {});
});

await waitFor(() => expect(loggingService.logError).toHaveBeenCalled());
});
});
});
7 changes: 6 additions & 1 deletion src/course-home/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMutation, useQuery } from '@tanstack/react-query';

import { useToast, ToastContent } from '@src/generic/ToastContext';
import {
executePostFromPostEvent, getCourseHomeCourseMetadata, getDatesTabData, postCourseDeadlines,
executePostFromPostEvent, getCourseHomeCourseMetadata, getDatesTabData, postCourseDeadlines, postRequestCert,
} from './api';
import { courseHomeQueryKeys } from './queryKeys';

Expand Down Expand Up @@ -60,3 +60,8 @@ export const useDatesTabData = (courseId: string) => useQuery({
queryFn: () => getDatesTabData(courseId),
meta: { modelType: 'dates', courseId },
});

export const useRequestCert = () => useMutation({
mutationFn: ({ courseId }: { courseId: string }) => postRequestCert(courseId),
onError: (error) => logError(error),
});
5 changes: 0 additions & 5 deletions src/course-home/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
deprecatedPostCourseGoals,
postWeeklyLearningGoal,
postDismissWelcomeMessage,
postRequestCert,
getLiveTabIframe,
} from './api';

Expand Down Expand Up @@ -105,10 +104,6 @@ export function dismissWelcomeMessage(courseId) {
return async () => postDismissWelcomeMessage(courseId);
}

export function requestCert(courseId) {
return async () => postRequestCert(courseId);
}

export async function deprecatedSaveCourseGoal(courseId, goalKey) {
return deprecatedPostCourseGoals(courseId, goalKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
useIntl,
} from '@edx/frontend-platform/i18n';
import { Alert, Button } from '@openedx/paragon';
import { useDispatch } from 'react-redux';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheckCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
Expand All @@ -15,7 +14,7 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import certMessages from './messages';
import certStatusMessages from '../../../progress-tab/certificate-status/messages';
import { requestCert } from '../../../data/thunks';
import { useRequestCert } from '../../../data/apiHooks';

export const CERT_STATUS_TYPE = {
EARNED_NOT_AVAILABLE: 'earned_but_not_available',
Expand All @@ -26,7 +25,7 @@ export const CERT_STATUS_TYPE = {

const CertificateStatusAlert = ({ payload }) => {
const intl = useIntl();
const dispatch = useDispatch();
const requestCert = useRequestCert();
const {
certificateAvailableDate,
certStatus,
Expand Down Expand Up @@ -91,7 +90,7 @@ const CertificateStatusAlert = ({ payload }) => {
alertProps.buttonLink = '';
alertProps.buttonAction = () => {
sendAlertClickTracking('edx.ui.lms.course_outline.certificate_alert_request_cert_button.clicked');
dispatch(requestCert(courseId));
requestCert.mutate({ courseId });
};
}
return alertProps;
Expand Down
6 changes: 5 additions & 1 deletion src/course-home/progress-tab/ProgressTab.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { breakpoints } from '@openedx/paragon';
import MockAdapter from 'axios-mock-adapter';

import {
fireEvent, initializeMockApp, logUnhandledRequests, render, screen, act,
fireEvent, initializeMockApp, logUnhandledRequests, render, screen, act, waitFor,
} from '../../setupTest';
import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils';
import * as thunks from '../data/thunks';
Expand Down Expand Up @@ -1020,6 +1020,10 @@ describe('Progress Tab', () => {
is_staff: false,
certificate_status_variant: 'requesting',
});

await waitFor(() => expect(
axiosMock.history.post.some(req => req.url.includes('generate_user_cert')),
).toBe(true));
});

it('Displays verify identity link', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { FormattedDate, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -10,7 +9,7 @@ import { useContextId } from '../../../data/hooks';
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 { requestCert } from '../../data/thunks';
import { useRequestCert } from '../../data/apiHooks';
import messages from './messages';
import ProgressCertificateStatusSlot from '../../../plugin-slots/ProgressCertificateStatusSlot';

Expand Down Expand Up @@ -62,7 +61,7 @@ const CertificateStatus = () => {
courserun_key: courseId,
};

const dispatch = useDispatch();
const requestCert = useRequestCert();
const { administrator } = getAuthenticatedUser();

let certStatus;
Expand Down Expand Up @@ -110,7 +109,7 @@ const CertificateStatus = () => {
switch (certStatus) {
case 'requesting':
certCase = 'requestable';
buttonAction = () => { dispatch(requestCert(courseId)); };
buttonAction = () => { requestCert.mutate({ courseId }); };
body = intl.formatMessage(messages[`${certCase}Body`]);
buttonText = intl.formatMessage(messages[`${certCase}Button`]);
break;
Expand Down
8 changes: 4 additions & 4 deletions src/courseware/course/course-exit/CourseCelebration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { faLinkedinIn } from '@fortawesome/free-brands-svg-icons';

import { FormattedDate, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Helmet } from 'react-helmet';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import {
Alert,
breakpoints,
Expand All @@ -23,7 +23,7 @@ import certificateLocked from '../../../generic/assets/openedx_locked_certificat
import { FormattedPricing } from '../../../generic/upgrade-button';
import messages from './messages';
import { useModel } from '../../../generic/model-store';
import { requestCert } from '../../../course-home/data/thunks';
import { useRequestCert } from '../../../course-home/data/apiHooks';
import ProgramCompletion from './ProgramCompletion';
import UpgradeFootnote from './UpgradeFootnote';
import SocialIcons from '../../social-share/SocialIcons';
Expand All @@ -38,7 +38,7 @@ const CourseCelebration = () => {
const intl = useIntl();
const wideScreen = useWindowSize().width >= breakpoints.medium.minWidth;
const { courseId } = useSelector(state => state.courseware);
const dispatch = useDispatch();
const requestCert = useRequestCert();
const {
certificateData,
end,
Expand Down Expand Up @@ -153,7 +153,7 @@ const CourseCelebration = () => {
variant={buttonVariant}
onClick={() => {
logClick(org, courseId, administrator, buttonEvent);
dispatch(requestCert(courseId));
requestCert.mutate({ courseId });
}}
>
{intl.formatMessage(messages.requestCertificateButton)}
Expand Down
10 changes: 10 additions & 0 deletions src/courseware/course/course-exit/CourseExit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { fetchCourse } from '../../data';
import { buildSimpleCourseBlocks } from '../../../shared/data/__factories__/courseBlocks.factory';
Expand Down Expand Up @@ -139,6 +140,15 @@ describe('Course Exit Pages', () => {
expect(screen.getByRole('button', { name: 'Request certificate' })).toBeInTheDocument();
});

it('requests the certificate when the request certificate link is clicked', async () => {
setMetadata({ certificate_data: { cert_status: 'requesting' } });
await fetchAndRender(<CourseCelebration />);
await userEvent.click(screen.getByRole('button', { name: 'Request certificate' }));
await waitFor(() => expect(
axiosMock.history.post.some(req => req.url.includes('generate_user_cert')),
).toBe(true));
});

it('Displays social share icons', async () => {
setMetadata({ certificate_data: { cert_status: 'unverified' }, marketing_url: 'https://edx.org' });
await fetchAndRender(<CourseCelebration />);
Expand Down