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.
- package/js/api/joanie.ts +26 -3
- package/js/api/lms/dummy.spec.ts +9 -1
- package/js/api/lms/dummy.ts +12 -2
- package/js/contexts/SessionContext/BaseSessionProvider.tsx +4 -12
- package/js/contexts/SessionContext/JoanieSessionProvider.tsx +4 -11
- package/js/contexts/SessionContext/index.spec.tsx +2 -4
- package/js/hooks/useContractArchive/index.download.spec.tsx +119 -0
- package/js/hooks/useContractArchive/index.spec.tsx +91 -0
- package/js/hooks/useContractArchive/index.ts +83 -0
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +136 -0
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +144 -0
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +73 -0
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +166 -0
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +23 -8
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +74 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +124 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +73 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +85 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +50 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +266 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +153 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.spec.tsx +100 -0
- package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +23 -0
- package/js/settings.ts +7 -0
- package/js/types/Joanie.ts +5 -0
- package/js/utils/errors/HttpError.ts +1 -0
- 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
|
+
});
|
package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx
ADDED
|
@@ -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
|
-
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
});
|
package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx
ADDED
|
@@ -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
|
+
});
|