richie-education 2.25.0-b2.dev32 → 2.25.0-b2.dev35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/js/api/joanie.ts +26 -3
  2. package/js/api/lms/dummy.spec.ts +9 -1
  3. package/js/api/lms/dummy.ts +12 -2
  4. package/js/contexts/SessionContext/BaseSessionProvider.tsx +4 -12
  5. package/js/contexts/SessionContext/JoanieSessionProvider.tsx +4 -11
  6. package/js/contexts/SessionContext/index.spec.tsx +2 -4
  7. package/js/hooks/useContractArchive/index.download.spec.tsx +119 -0
  8. package/js/hooks/useContractArchive/index.spec.tsx +91 -0
  9. package/js/hooks/useContractArchive/index.ts +83 -0
  10. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +136 -0
  11. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +144 -0
  12. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +73 -0
  13. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +166 -0
  14. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +23 -8
  15. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +74 -0
  16. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +124 -0
  17. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +73 -0
  18. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +85 -0
  19. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +50 -0
  20. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +266 -0
  21. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +153 -0
  22. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.spec.tsx +100 -0
  23. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +23 -0
  24. package/js/settings.ts +7 -0
  25. package/js/types/Joanie.ts +5 -0
  26. package/js/utils/errors/HttpError.ts +1 -0
  27. package/package.json +1 -1
@@ -0,0 +1,144 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
4
+ import { PropsWithChildren } from 'react';
5
+ import { QueryClientProvider } from '@tanstack/react-query';
6
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
+ import JoanieApiProvider from 'contexts/JoanieApiContext';
8
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
9
+
10
+ import {
11
+ getStoredContractArchiveId,
12
+ storeContractArchiveId,
13
+ unstoreContractArchiveId,
14
+ } from 'pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage';
15
+ import { CONTRACT_DOWNLOAD_SETTINGS } from 'settings';
16
+ import BulkDownloadContractButton from '.';
17
+
18
+ jest.mock('utils/context', () => ({
19
+ __esModule: true,
20
+ default: mockRichieContextFactory({
21
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
22
+ joanie_backend: { endpoint: 'https://joanie.test' },
23
+ }).one(),
24
+ }));
25
+
26
+ let mockHasContractToDownload: boolean;
27
+ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx', () => ({
28
+ __esModule: true,
29
+ default: () => mockHasContractToDownload,
30
+ }));
31
+
32
+ const mockCheckArchive = jest.fn();
33
+ const mockCreateArchive = jest.fn();
34
+ const mockGetArchive = jest.fn();
35
+ jest.mock('hooks/useContractArchive', () => ({
36
+ __esModule: true,
37
+ default: () => ({
38
+ methods: { get: mockGetArchive, create: mockCreateArchive, check: mockCheckArchive },
39
+ }),
40
+ }));
41
+
42
+ describe('TeacherDashboardContractsLayout/BulkDownloadContractButton with fake timer', () => {
43
+ const Wrapper = ({ children }: PropsWithChildren) => {
44
+ return (
45
+ <IntlProvider locale="en">
46
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
47
+ <JoanieApiProvider>{children}</JoanieApiProvider>
48
+ </QueryClientProvider>
49
+ </IntlProvider>
50
+ );
51
+ };
52
+
53
+ let contractArchiveId: string;
54
+ beforeEach(() => {
55
+ mockHasContractToDownload = true;
56
+
57
+ const now = Date.now();
58
+ const unvalidCreationTime =
59
+ now - CONTRACT_DOWNLOAD_SETTINGS.contractArchiveLocalVaklidityDurationMs * 2;
60
+
61
+ jest.useFakeTimers();
62
+ jest.setSystemTime(new Date(unvalidCreationTime));
63
+ contractArchiveId = faker.string.uuid();
64
+ storeContractArchiveId(contractArchiveId);
65
+ jest.setSystemTime(new Date(now));
66
+ });
67
+
68
+ afterEach(() => {
69
+ jest.runOnlyPendingTimers();
70
+ jest.useRealTimers();
71
+ jest.clearAllMocks();
72
+ unstoreContractArchiveId();
73
+ });
74
+
75
+ it("should return IDLE status and clear stored id when archive doesn't exists on the server", async () => {
76
+ mockHasContractToDownload = true;
77
+ mockCheckArchive.mockResolvedValue(false);
78
+
79
+ render(
80
+ <Wrapper>
81
+ <BulkDownloadContractButton organizationId={faker.string.uuid()} />
82
+ </Wrapper>,
83
+ );
84
+
85
+ const $downloadButton = screen.queryByRole('button', {
86
+ name: /Download contracts archive/,
87
+ });
88
+ expect($downloadButton).toBeInTheDocument();
89
+
90
+ // BulkDownloadButton is disabled until initiliazed
91
+ expect($downloadButton).toBeDisabled();
92
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
93
+
94
+ // Button should initalize with idle state and propose an archive generation
95
+ await waitFor(() => {
96
+ const $createArchiveButton = screen.queryByRole('button', {
97
+ name: /Request contracts archive/,
98
+ });
99
+ expect($createArchiveButton).toBeInTheDocument();
100
+ expect($createArchiveButton).toBeEnabled();
101
+ });
102
+
103
+ expect(getStoredContractArchiveId()).toBe(null);
104
+ expect(mockGetArchive).not.toHaveBeenCalled();
105
+ expect(mockCreateArchive).not.toHaveBeenCalled();
106
+
107
+ // polling shouldn't start, mockCheckArchive shouldn't been called more than once
108
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
109
+ });
110
+
111
+ it('should return READY status when archive exists on the server', async () => {
112
+ mockHasContractToDownload = true;
113
+ mockCheckArchive.mockResolvedValue(true);
114
+
115
+ render(
116
+ <Wrapper>
117
+ <BulkDownloadContractButton organizationId={faker.string.uuid()} />
118
+ </Wrapper>,
119
+ );
120
+
121
+ const $initButton = screen.queryByRole('button', {
122
+ name: /Download contracts archive/,
123
+ });
124
+ expect($initButton).toBeInTheDocument();
125
+ // BulkDownloadButton is disabled until initiliazed
126
+ expect($initButton).toBeDisabled();
127
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
128
+
129
+ await waitFor(() => {
130
+ const $downloadButton = screen.queryByRole('button', {
131
+ name: /Download contracts archive/,
132
+ });
133
+ expect($downloadButton).toBeInTheDocument();
134
+ expect($downloadButton).toBeEnabled();
135
+ });
136
+
137
+ expect(getStoredContractArchiveId()).toBe(contractArchiveId);
138
+ expect(mockGetArchive).not.toHaveBeenCalled();
139
+ expect(mockCreateArchive).not.toHaveBeenCalled();
140
+
141
+ // polling shouldn't start, mockCheckArchive shouldn't been called more than once
142
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
143
+ });
144
+ });
@@ -0,0 +1,73 @@
1
+ import { Button } from '@openfun/cunningham-react';
2
+ import { FormattedMessage, defineMessages } from 'react-intl';
3
+ import { useEffect } from 'react';
4
+ import useDownloadContractArchive, {
5
+ ContractDownloadStatus,
6
+ } from 'pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive';
7
+ import { Organization } from 'types/Joanie';
8
+
9
+ const messages = defineMessages({
10
+ bulkDownloadButtonDownloadLabel: {
11
+ defaultMessage: 'Download contracts archive',
12
+ description: 'The label of the bulk download button when the zip archive is ready for download',
13
+ id: 'pages.TeacherDashboardContractsLayout.BulkDownloadContractButton.bulkDownloadButtonDownloadLabel',
14
+ },
15
+ bulkDownloadButtonPendingLabel: {
16
+ defaultMessage: 'Generating contracts archive...',
17
+ description: 'The label of the bulk download button when archive generation is pending',
18
+ id: 'pages.TeacherDashboardContractsLayout.BulkDownloadContractButton.bulkDownloadButtonPendingLabel',
19
+ },
20
+ bulkDownloadButtonRequestArchiveLabel: {
21
+ defaultMessage: 'Request contracts archive',
22
+ description: 'The label of the bulk download button to request the generation of a zip archive',
23
+ id: 'pages.TeacherDashboardContractsLayout.BulkDownloadContractButton.bulkDownloadButtonRequestArchiveLabel',
24
+ },
25
+ });
26
+
27
+ interface BulkDownloadContractButtonProps {
28
+ organizationId: Organization['id'];
29
+ }
30
+
31
+ const BulkDownloadContractButton = ({ organizationId }: BulkDownloadContractButtonProps) => {
32
+ const { downloadContractArchive, createContractArchive, status } = useDownloadContractArchive({
33
+ organizationId,
34
+ });
35
+
36
+ useEffect(() => {
37
+ // Trigger contract's archive polling when generation had already been requested
38
+ if (status === ContractDownloadStatus.PENDING) {
39
+ createContractArchive();
40
+ }
41
+ }, [status]);
42
+
43
+ if (status === ContractDownloadStatus.PENDING) {
44
+ return (
45
+ <Button
46
+ disabled={true}
47
+ color="tertiary"
48
+ size="small"
49
+ icon={<div className="spinner spinner--small" />}
50
+ >
51
+ <FormattedMessage {...messages.bulkDownloadButtonPendingLabel} />
52
+ </Button>
53
+ );
54
+ }
55
+
56
+ return (
57
+ <Button
58
+ onClick={downloadContractArchive}
59
+ disabled={status === ContractDownloadStatus.INITIALIZING}
60
+ color={status === ContractDownloadStatus.READY ? 'primary' : 'tertiary'}
61
+ size="small"
62
+ icon={<span className="material-icons">download</span>}
63
+ >
64
+ <FormattedMessage
65
+ {...(status === ContractDownloadStatus.IDLE
66
+ ? messages.bulkDownloadButtonRequestArchiveLabel
67
+ : messages.bulkDownloadButtonDownloadLabel)}
68
+ />
69
+ </Button>
70
+ );
71
+ };
72
+
73
+ export default BulkDownloadContractButton;
@@ -0,0 +1,166 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
4
+ import { PropsWithChildren } from 'react';
5
+ import { QueryClientProvider } from '@tanstack/react-query';
6
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
+ import JoanieApiProvider from 'contexts/JoanieApiContext';
8
+
9
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
10
+ import { ContractDownloadStatus } from 'pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive';
11
+ import ContractActionsBar from '.';
12
+
13
+ jest.mock('utils/context', () => ({
14
+ __esModule: true,
15
+ default: mockRichieContextFactory({
16
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
17
+ joanie_backend: { endpoint: 'https://joanie.test' },
18
+ }).one(),
19
+ }));
20
+
21
+ let mockCanSignContracts: boolean;
22
+ let mockContractsToSignCount: number;
23
+ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign', () => ({
24
+ __esModule: true,
25
+ default: () => ({
26
+ canSignContracts: mockCanSignContracts,
27
+ contractsToSignCount: mockContractsToSignCount,
28
+ }),
29
+ }));
30
+
31
+ let mockDownloadContractArchive: () => Promise<void>;
32
+ let mockCreateContractArchive: () => Promise<void>;
33
+ let mockDownloadContractArchiveStatus: ContractDownloadStatus;
34
+ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive', () => ({
35
+ __esModule: true,
36
+ ...jest.requireActual('pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive'),
37
+ default: () => ({
38
+ downloadArchive: mockDownloadContractArchive,
39
+ createContractArchive: mockCreateContractArchive,
40
+ status: mockDownloadContractArchiveStatus,
41
+ }),
42
+ }));
43
+
44
+ let mockHasContractToDownload: boolean;
45
+ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx', () => ({
46
+ __esModule: true,
47
+ default: () => mockHasContractToDownload,
48
+ }));
49
+
50
+ describe('TeacherDashboardContractsLayout/ContractActionsBar', () => {
51
+ const Wrapper = ({ children }: PropsWithChildren) => {
52
+ return (
53
+ <IntlProvider locale="en">
54
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
55
+ <JoanieApiProvider>{children}</JoanieApiProvider>
56
+ </QueryClientProvider>
57
+ </IntlProvider>
58
+ );
59
+ };
60
+
61
+ beforeAll(() => {
62
+ const modalExclude = document.createElement('div');
63
+ modalExclude.setAttribute('id', 'modal-exclude');
64
+ document.body.appendChild(modalExclude);
65
+ });
66
+
67
+ beforeEach(() => {
68
+ // useTeacherContractsToSign mocked values
69
+ mockCanSignContracts = true;
70
+ mockContractsToSignCount = 1;
71
+
72
+ // useDownloadContractArchive mocked values
73
+ mockHasContractToDownload = false;
74
+ mockDownloadContractArchive = jest.fn(() => Promise.resolve());
75
+ mockCreateContractArchive = jest.fn(() => Promise.resolve());
76
+ mockDownloadContractArchiveStatus = ContractDownloadStatus.IDLE;
77
+ });
78
+
79
+ afterEach(() => {
80
+ jest.resetAllMocks();
81
+ });
82
+
83
+ it("shouldn't display both sign and download button", () => {
84
+ mockHasContractToDownload = true;
85
+ mockCanSignContracts = true;
86
+ mockContractsToSignCount = 1;
87
+
88
+ render(
89
+ <Wrapper>
90
+ <ContractActionsBar
91
+ courseProductRelationId={faker.string.uuid()}
92
+ organizationId={faker.string.uuid()}
93
+ />
94
+ </Wrapper>,
95
+ );
96
+
97
+ expect(screen.getByTestId('teacher-contracts-list-actionsBar')).toBeInTheDocument();
98
+ expect(screen.getByRole('button', { name: /Sign all pending contracts/ })).toBeInTheDocument();
99
+ expect(screen.getByRole('button', { name: /Request contracts archive/ })).toBeInTheDocument();
100
+ });
101
+
102
+ it("shouldn't only display sign button", () => {
103
+ mockHasContractToDownload = false;
104
+ mockCanSignContracts = true;
105
+ mockContractsToSignCount = 1;
106
+
107
+ render(
108
+ <Wrapper>
109
+ <ContractActionsBar
110
+ courseProductRelationId={faker.string.uuid()}
111
+ organizationId={faker.string.uuid()}
112
+ />
113
+ </Wrapper>,
114
+ );
115
+
116
+ expect(screen.getByTestId('teacher-contracts-list-actionsBar')).toBeInTheDocument();
117
+ expect(screen.getByRole('button', { name: /Sign all pending contracts/ })).toBeInTheDocument();
118
+ expect(
119
+ screen.queryByRole('button', { name: /Request contracts archive/ }),
120
+ ).not.toBeInTheDocument();
121
+ });
122
+
123
+ it("shouldn't only display download button", () => {
124
+ mockHasContractToDownload = true;
125
+ mockCanSignContracts = false;
126
+ mockContractsToSignCount = 0;
127
+
128
+ render(
129
+ <Wrapper>
130
+ <ContractActionsBar
131
+ courseProductRelationId={faker.string.uuid()}
132
+ organizationId={faker.string.uuid()}
133
+ />
134
+ </Wrapper>,
135
+ );
136
+
137
+ expect(screen.getByTestId('teacher-contracts-list-actionsBar')).toBeInTheDocument();
138
+ expect(screen.getByRole('button', { name: /Request contracts archive/ })).toBeInTheDocument();
139
+ expect(
140
+ screen.queryByRole('button', { name: /Sign all pending contracts/ }),
141
+ ).not.toBeInTheDocument();
142
+ });
143
+
144
+ it('should return nothing when no actions are available', () => {
145
+ mockHasContractToDownload = false;
146
+ mockCanSignContracts = false;
147
+ mockContractsToSignCount = 0;
148
+
149
+ render(
150
+ <Wrapper>
151
+ <ContractActionsBar
152
+ courseProductRelationId={faker.string.uuid()}
153
+ organizationId={faker.string.uuid()}
154
+ />
155
+ </Wrapper>,
156
+ );
157
+
158
+ expect(screen.queryByTestId('teacher-contracts-list-actionsBar')).not.toBeInTheDocument();
159
+ expect(
160
+ screen.queryByRole('button', { name: /Request contracts archive/ }),
161
+ ).not.toBeInTheDocument();
162
+ expect(
163
+ screen.queryByRole('button', { name: /Sign all pending contracts/ }),
164
+ ).not.toBeInTheDocument();
165
+ });
166
+ });
@@ -1,6 +1,9 @@
1
+ import classNames from 'classnames';
1
2
  import { Organization, CourseProductRelation } from 'types/Joanie';
2
3
  import useTeacherContractsToSign from 'pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign';
4
+ import useHasContractToDownload from 'pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload';
3
5
  import SignOrganizationContractButton from '../SignOrganizationContractButton';
6
+ import BulkDownloadContractButton from '../BulkDownloadContractButton';
4
7
 
5
8
  interface ContractActionsProps {
6
9
  organizationId: Organization['id'];
@@ -12,15 +15,27 @@ const ContractActionsBar = ({ organizationId, courseProductRelationId }: Contrac
12
15
  organizationId,
13
16
  courseProductRelationId,
14
17
  });
18
+ const hasContractToDownload = useHasContractToDownload(organizationId);
19
+
20
+ const nbAvailableActions = [canSignContracts, hasContractToDownload].filter((val) => val).length;
15
21
  return (
16
- canSignContracts && (
17
- <div className="dashboard__page__actions-row dashboard__page__actions-row--space-between">
18
- <div>
19
- <SignOrganizationContractButton
20
- organizationId={organizationId}
21
- contractToSignCount={contractsToSignCount}
22
- />
23
- </div>
22
+ nbAvailableActions > 0 && (
23
+ <div
24
+ className={classNames('dashboard__page__actions-row', {
25
+ 'dashboard__page__actions-row--space-between': nbAvailableActions > 1,
26
+ 'dashboard__page__actions-row--end': nbAvailableActions === 1,
27
+ })}
28
+ data-testid="teacher-contracts-list-actionsBar"
29
+ >
30
+ {canSignContracts && (
31
+ <div>
32
+ <SignOrganizationContractButton
33
+ organizationId={organizationId}
34
+ contractToSignCount={contractsToSignCount}
35
+ />
36
+ </div>
37
+ )}
38
+ {hasContractToDownload && <BulkDownloadContractButton organizationId={organizationId} />}
24
39
  </div>
25
40
  )
26
41
  );
@@ -0,0 +1,74 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
4
+ import { PropsWithChildren } from 'react';
5
+ import { QueryClientProvider } from '@tanstack/react-query';
6
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
+ import JoanieApiProvider from 'contexts/JoanieApiContext';
8
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
9
+
10
+ import SignOrganizationContractButton from '.';
11
+
12
+ jest.mock('utils/context', () => ({
13
+ __esModule: true,
14
+ default: mockRichieContextFactory({
15
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
16
+ joanie_backend: { endpoint: 'https://joanie.test' },
17
+ }).one(),
18
+ }));
19
+
20
+ describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () => {
21
+ const Wrapper = ({ children }: PropsWithChildren) => {
22
+ return (
23
+ <IntlProvider locale="en">
24
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
25
+ <JoanieApiProvider>{children}</JoanieApiProvider>
26
+ </QueryClientProvider>
27
+ </IntlProvider>
28
+ );
29
+ };
30
+
31
+ beforeAll(() => {
32
+ const modalExclude = document.createElement('div');
33
+ modalExclude.setAttribute('id', 'modal-exclude');
34
+ document.body.appendChild(modalExclude);
35
+ });
36
+
37
+ afterEach(() => {
38
+ jest.resetAllMocks();
39
+ });
40
+
41
+ it("shouldn't render sign button and <OrganizationContractFrame/> when contractToSignCount > 0", () => {
42
+ render(
43
+ <Wrapper>
44
+ <SignOrganizationContractButton
45
+ organizationId={faker.string.uuid()}
46
+ contractToSignCount={1}
47
+ />
48
+ </Wrapper>,
49
+ );
50
+
51
+ expect(screen.getByRole('button', { name: /Sign all pending contracts/ })).toBeInTheDocument();
52
+
53
+ const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
54
+ expect(DashboardContractFramePortal).toHaveLength(1);
55
+ });
56
+
57
+ it("shouldn't only render <OrganizationContractFrame/> when contractToSignCount is 0", () => {
58
+ render(
59
+ <Wrapper>
60
+ <SignOrganizationContractButton
61
+ organizationId={faker.string.uuid()}
62
+ contractToSignCount={0}
63
+ />
64
+ </Wrapper>,
65
+ );
66
+
67
+ expect(
68
+ screen.queryByRole('button', { name: /Sign all pending contracts/ }),
69
+ ).not.toBeInTheDocument();
70
+
71
+ const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
72
+ expect(DashboardContractFramePortal).toHaveLength(1);
73
+ });
74
+ });
@@ -0,0 +1,124 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import fetchMock from 'fetch-mock';
3
+ import { act, renderHook, waitFor } from '@testing-library/react';
4
+ import { IntlProvider } from 'react-intl';
5
+ import { PropsWithChildren } from 'react';
6
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
+ import JoanieApiProvider from 'contexts/JoanieApiContext';
8
+ import {
9
+ storeContractArchiveId,
10
+ unstoreContractArchiveId,
11
+ } from '../useDownloadContractArchive/contractArchiveLocalStorage';
12
+ import useCheckContractArchiveExists from '.';
13
+
14
+ jest.mock('utils/context', () => ({
15
+ __esModule: true,
16
+ default: mockRichieContextFactory({
17
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
18
+ joanie_backend: { endpoint: 'https://joanie.test' },
19
+ }).one(),
20
+ }));
21
+
22
+ jest.mock('settings', () => ({
23
+ ...jest.requireActual('settings'),
24
+ CONTRACT_DOWNLOAD_SETTINGS: {
25
+ ...jest.requireActual('settings').CONTRACT_DOWNLOAD_SETTINGS,
26
+ pollInterval: 100,
27
+ },
28
+ }));
29
+
30
+ const mockCheckArchive = jest.fn();
31
+ jest.mock('hooks/useContractArchive', () => ({
32
+ __esModule: true,
33
+ default: () => ({
34
+ methods: { get: jest.fn(), create: jest.fn(), check: mockCheckArchive },
35
+ }),
36
+ }));
37
+
38
+ describe('useCheckContractArchiveExists', () => {
39
+ const Wrapper = ({ children }: PropsWithChildren) => {
40
+ return (
41
+ <IntlProvider locale="en">
42
+ <JoanieApiProvider>{children}</JoanieApiProvider>
43
+ </IntlProvider>
44
+ );
45
+ };
46
+ beforeEach(() => {
47
+ // Joanie providers calls
48
+ fetchMock.get('https://joanie.test/api/v1.0/orders/', []);
49
+ fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
50
+ fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
51
+ });
52
+
53
+ afterEach(() => {
54
+ jest.resetAllMocks();
55
+ fetchMock.restore();
56
+ unstoreContractArchiveId();
57
+ });
58
+
59
+ it('should do nothing and return default value when no contractArchiveId is stored', () => {
60
+ const { result } = renderHook(useCheckContractArchiveExists, {
61
+ wrapper: Wrapper,
62
+ });
63
+
64
+ expect(result.current.isContractArchiveExists).toBe(false);
65
+ expect(mockCheckArchive).not.toHaveBeenCalled();
66
+ });
67
+
68
+ it('should check if archive exist when a id is stored', async () => {
69
+ storeContractArchiveId(faker.string.uuid());
70
+ mockCheckArchive.mockResolvedValue(true);
71
+
72
+ const { result } = renderHook(useCheckContractArchiveExists, {
73
+ wrapper: Wrapper,
74
+ });
75
+
76
+ expect(result.current.isContractArchiveExists).toBeNull();
77
+ await waitFor(() => {
78
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
79
+ });
80
+
81
+ expect(result.current.isPolling).toBe(false);
82
+ expect(result.current.isContractArchiveExists).toBe(true);
83
+ });
84
+
85
+ it('should do nothing when enable is false', () => {
86
+ storeContractArchiveId(faker.string.uuid());
87
+ mockCheckArchive.mockResolvedValue(true);
88
+
89
+ const { result } = renderHook(() => useCheckContractArchiveExists({ enable: false }), {
90
+ wrapper: Wrapper,
91
+ });
92
+
93
+ expect(result.current.isContractArchiveExists).toBe(false);
94
+ expect(mockCheckArchive).not.toHaveBeenCalled();
95
+ });
96
+
97
+ it('should trigger polling when checkArchiveExist is call', async () => {
98
+ const { result, rerender } = renderHook(useCheckContractArchiveExists, {
99
+ wrapper: Wrapper,
100
+ });
101
+
102
+ mockCheckArchive.mockResolvedValue(false);
103
+ act(() => {
104
+ result.current.checkArchiveExists(faker.string.uuid());
105
+ });
106
+
107
+ await waitFor(() => {
108
+ expect(mockCheckArchive).toHaveBeenCalledTimes(1);
109
+ });
110
+
111
+ expect(result.current.isContractArchiveExists).toBe(false);
112
+
113
+ // isPolling it need's a rerender to be updated
114
+ rerender();
115
+ expect(result.current.isPolling).toBe(true);
116
+
117
+ mockCheckArchive.mockResolvedValue(true);
118
+ await waitFor(() => {
119
+ expect(mockCheckArchive).toHaveBeenCalledTimes(2);
120
+ });
121
+ expect(result.current.isPolling).toBe(false);
122
+ expect(result.current.isContractArchiveExists).toBe(true);
123
+ });
124
+ });