diff --git a/CHANGELOG.md b/CHANGELOG.md
index e64d35dde0..52ab59114e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).
## Added
+- The enrollment's certificate tab in learner dashbaord is no longer
+ displayed when the list is empty.
- User profile in the learner dashboard is now always synchronized with
openEdx profile informations.
- Add a tab in learner dashboard certificate page to render both order
diff --git a/src/frontend/js/pages/DashboardCertificates/components/CertificateList/index.tsx b/src/frontend/js/pages/DashboardCertificates/components/CertificateList/index.tsx
index 2b5d772687..55008777ef 100644
--- a/src/frontend/js/pages/DashboardCertificates/components/CertificateList/index.tsx
+++ b/src/frontend/js/pages/DashboardCertificates/components/CertificateList/index.tsx
@@ -8,6 +8,7 @@ import { Spinner } from 'components/Spinner';
import Banner, { BannerType } from 'components/Banner';
import { DashboardItemCertificate } from 'widgets/Dashboard/components/DashboardItem/Certificate';
import { CertificateType } from 'types/Joanie';
+import { PER_PAGE } from 'settings';
const messages = defineMessages({
loading: {
@@ -27,7 +28,7 @@ interface CertificatesListProps {
}
const CertificatesList = ({ certificateType }: CertificatesListProps) => {
const intl = useIntl();
- const pagination = usePagination({});
+ const pagination = usePagination({ itemsPerPage: PER_PAGE.certificateList });
const certificates = useCertificates(
{
type: certificateType,
diff --git a/src/frontend/js/pages/DashboardCertificates/index.spec.tsx b/src/frontend/js/pages/DashboardCertificates/index.spec.tsx
index 6deda585bc..02f21a3dc3 100644
--- a/src/frontend/js/pages/DashboardCertificates/index.spec.tsx
+++ b/src/frontend/js/pages/DashboardCertificates/index.spec.tsx
@@ -1,22 +1,25 @@
import { render, screen, waitFor } from '@testing-library/react';
-import { QueryClientProvider } from '@tanstack/react-query';
-import { IntlProvider } from 'react-intl';
import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
-import { createTestQueryClient } from 'utils/test/createTestQueryClient';
import { Certificate } from 'types/Joanie';
import { resolveAll } from 'utils/resolveAll';
import { expectNoSpinner, expectSpinner } from 'utils/test/expectSpinner';
import { expectBannerError, expectBannerInfo } from 'utils/test/expectBanner';
import { Deferred } from 'utils/test/deferred';
-import { History, HistoryContext } from 'hooks/useHistory';
-import { SessionProvider } from 'contexts/SessionContext';
+import * as mockUseHistory from 'hooks/useHistory';
import { DashboardTest } from 'widgets/Dashboard/components/DashboardTest';
import { CertificateFactory } from 'utils/test/factories/joanie';
import { HttpStatusCode } from 'utils/errors/HttpError';
import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
+import { BaseJoanieAppWrapper } from 'utils/test/wrappers/BaseJoanieAppWrapper';
+
+jest.mock('hooks/useHistory', () => ({
+ __esModule: true,
+ ...mockUseHistory,
+ useHistory: () => [jest.fn(), jest.fn(), jest.fn()],
+}));
jest.mock('utils/context', () => ({
__esModule: true,
@@ -32,18 +35,6 @@ jest.mock('utils/indirection/window', () => ({
}));
describe('', () => {
- const historyPushState = jest.fn();
- const historyReplaceState = jest.fn();
- const makeHistoryOf: (params: any) => History = () => [
- {
- state: { name: '', data: {} },
- title: '',
- url: `/`,
- },
- historyPushState,
- historyReplaceState,
- ];
-
beforeEach(() => {
fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', []);
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', []);
@@ -55,32 +46,62 @@ describe('', () => {
fetchMock.restore();
});
- it('renders an empty list of certificates', async () => {
- fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=10', {
+ it('should render both certificate tabs', async () => {
+ fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=25', {
results: [],
next: null,
previous: null,
count: 30,
});
+ fetchMock.get(
+ 'https://joanie.endpoint/api/v1.0/certificates/?type=enrollment&page=1&page_size=25',
+ {
+ results: [CertificateFactory().one()],
+ next: null,
+ previous: null,
+ count: 1,
+ },
+ );
+
+ render(, {
+ wrapper: BaseJoanieAppWrapper,
+ });
- render(
-
-
-
-
-
-
-
-
- ,
+ // Make sure the spinner appear during first load.
+ await expectSpinner('Loading certificates...');
+ await expectNoSpinner('Loading certificates...');
+ expect(screen.queryByTestId('tabs-header')).toBeInTheDocument();
+ });
+
+ it('renders an empty list of certificates', async () => {
+ fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=25', {
+ results: [],
+ next: null,
+ previous: null,
+ count: 30,
+ });
+ fetchMock.get(
+ 'https://joanie.endpoint/api/v1.0/certificates/?type=enrollment&page=1&page_size=25',
+ {
+ results: [],
+ next: null,
+ previous: null,
+ count: 0,
+ },
);
+ render(, {
+ wrapper: BaseJoanieAppWrapper,
+ });
+
// Make sure the spinner appear during first load.
await expectSpinner('Loading certificates...');
await expectNoSpinner('Loading certificates...');
await expectBannerInfo('You have no certificates yet.');
+
+ expect(screen.queryByTestId('tabs-header')).not.toBeInTheDocument();
});
it('renders 3 pages of certificates', async () => {
@@ -88,30 +109,31 @@ describe('', () => {
const certificatesPage1 = certificates.slice(0, 10);
const certificatesPage2 = certificates.slice(10, 20);
- fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=10', {
+ fetchMock.get(
+ 'https://joanie.endpoint/api/v1.0/certificates/?type=enrollment&page=1&page_size=25',
+ {
+ results: [],
+ next: null,
+ previous: null,
+ count: 0,
+ },
+ );
+ fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=25', {
results: certificatesPage1,
next: null,
previous: null,
- count: 30,
+ count: 60,
});
const page2Deferred = new Deferred();
fetchMock.get(
- 'https://joanie.endpoint/api/v1.0/certificates/?type=order&page=2&page_size=10',
+ 'https://joanie.endpoint/api/v1.0/certificates/?type=order&page=2&page_size=25',
page2Deferred.promise,
);
- render(
-
-
-
-
-
-
-
-
- ,
- );
+ render(, {
+ wrapper: BaseJoanieAppWrapper,
+ });
// Make sure the spinner appear during first load.
await expectSpinner('Loading certificates...');
@@ -126,7 +148,7 @@ describe('', () => {
});
// Go to page 2.
- await userEvent.click(screen.getByText('Next page 2'));
+ await userEvent.click(await screen.findByText('Next page 2'));
// Make sure the loading class is set.
await waitFor(() =>
@@ -153,30 +175,33 @@ describe('', () => {
await resolveAll(certificatesPage1, async (certificate) => {
await screen.findByText(certificate.certificate_definition.title);
});
+ expect(screen.queryByTestId('tabs-header')).not.toBeInTheDocument();
});
it('shows an error when request to retrieve certificates fails', async () => {
- fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=10', {
+ fetchMock.get(
+ 'https://joanie.endpoint/api/v1.0/certificates/?type=enrollment&page=1&page_size=25',
+ {
+ results: [],
+ next: null,
+ previous: null,
+ count: 0,
+ },
+ );
+ fetchMock.get('https://joanie.endpoint/api/v1.0/certificates/?type=order&page=1&page_size=25', {
status: HttpStatusCode.INTERNAL_SERVER_ERROR,
body: 'Internal Server Error',
});
- render(
-
-
-
-
-
-
-
-
- ,
- );
+ render(, {
+ wrapper: BaseJoanieAppWrapper,
+ });
// Make sure error is shown.
await expectBannerError('An error occurred while fetching certificates. Please retry later.');
// ... and the spinner hidden.
await expectNoSpinner('Loading ...');
+ expect(screen.queryByTestId('tabs-header')).not.toBeInTheDocument();
});
});
diff --git a/src/frontend/js/pages/DashboardCertificates/index.tsx b/src/frontend/js/pages/DashboardCertificates/index.tsx
index 0fd3e9a2dd..6dd17953fe 100644
--- a/src/frontend/js/pages/DashboardCertificates/index.tsx
+++ b/src/frontend/js/pages/DashboardCertificates/index.tsx
@@ -1,6 +1,8 @@
import { FormattedMessage, defineMessages } from 'react-intl';
import { CertificateType } from 'types/Joanie';
import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
+import { useCertificates } from 'hooks/useCertificates';
+import { PER_PAGE } from 'settings';
import Tabs from '../../components/Tabs';
import CertificatesList from './components/CertificateList';
@@ -22,25 +24,31 @@ interface DashboardCertificatesProps {
}
export const DashboardCertificates = ({ certificateType }: DashboardCertificatesProps) => {
+ const {
+ items: enrollmentCertificates,
+ states: { isFetched },
+ } = useCertificates({
+ type: CertificateType.ENROLLMENT,
+ page: 1,
+ page_size: PER_PAGE.certificateList,
+ });
+
return (
-
-
-
-
-
-
-
-
+ {isFetched && enrollmentCertificates.length > 0 && (
+
+
+
+
+
+
+
+
+ )}
+
diff --git a/src/frontend/js/settings/settings.prod.ts b/src/frontend/js/settings/settings.prod.ts
index b5688dd2e7..c8ab7e7ab7 100644
--- a/src/frontend/js/settings/settings.prod.ts
+++ b/src/frontend/js/settings/settings.prod.ts
@@ -54,6 +54,7 @@ export const CONTRACT_DOWNLOAD_SETTINGS = {
const DEFAULT_PER_PAGE = 50;
export const PER_PAGE = {
teacherContractList: 25,
+ certificateList: 25,
courseLearnerList: DEFAULT_PER_PAGE,
useUnionResources: DEFAULT_PER_PAGE,
useCourseProductUnion: DEFAULT_PER_PAGE,