richie-education 2.28.2-dev39 → 2.28.2-dev53
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 +12 -16
- package/js/api/lms/dummy.ts +1 -12
- package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
- package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
- package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
- package/js/components/ContractFrame/_styles.scss +6 -14
- package/js/components/CreditCardSelector/index.spec.tsx +7 -7
- package/js/components/CreditCardSelector/index.tsx +2 -2
- package/js/components/DownloadContractButton/index.spec.tsx +1 -1
- package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
- package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
- package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
- package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
- package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
- package/js/components/PaymentInterfaces/types.ts +5 -2
- package/js/components/PurchaseButton/index.spec.tsx +69 -37
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
- package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
- package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +75 -41
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +0 -30
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
- package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +5 -0
- package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
- package/js/components/SaleTunnel/_styles.scss +10 -1
- package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
- package/js/components/SaleTunnel/index.credential.spec.tsx +12 -21
- package/js/components/SaleTunnel/index.full-process.spec.tsx +110 -48
- package/js/components/SaleTunnel/index.spec.tsx +330 -779
- package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
- package/js/components/SignContractButton/index.spec.tsx +16 -20
- package/js/components/SignContractButton/index.tsx +3 -1
- package/js/hooks/useCreditCards/index.spec.tsx +70 -6
- package/js/hooks/useCreditCards/index.ts +49 -11
- package/js/hooks/useOrders/index.spec.tsx +322 -0
- package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
- package/js/hooks/useProductOrder/index.spec.tsx +77 -60
- package/js/hooks/useProductOrder/index.tsx +2 -2
- package/js/hooks/useResources/useResourcesRoot.ts +1 -0
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
- package/js/settings/settings.test.ts +11 -2
- package/js/types/Joanie.ts +49 -34
- package/js/utils/OrderHelper/index.ts +38 -42
- package/js/utils/test/factories/joanie.ts +36 -51
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +7 -6
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +9 -10
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +28 -8
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +2 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +34 -35
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +27 -24
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
- package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
- package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
- package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
- package/package.json +1 -1
- package/scss/components/_index.scss +2 -1
- package/js/components/PaymentButton/_styles.scss +0 -27
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -333
- package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
|
@@ -5,11 +5,12 @@ import { MemoryRouter } from 'react-router-dom';
|
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
7
7
|
import fetchMock from 'fetch-mock';
|
|
8
|
-
import {
|
|
8
|
+
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
9
9
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
10
|
+
import { ContractFactory, CredentialOrderFactory } from 'utils/test/factories/joanie';
|
|
10
11
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
11
12
|
import { useOmniscientOrders } from 'hooks/useOrders';
|
|
12
|
-
import { CredentialOrder } from 'types/Joanie';
|
|
13
|
+
import { OrderState, CredentialOrder } from 'types/Joanie';
|
|
13
14
|
import { SessionProvider } from 'contexts/SessionContext';
|
|
14
15
|
import SignContractButton from '.';
|
|
15
16
|
|
|
@@ -30,13 +31,15 @@ jest.mock('settings', () => ({
|
|
|
30
31
|
describe('<SignContractButton/>', () => {
|
|
31
32
|
const Wrapper = ({ children }: PropsWithChildren) => {
|
|
32
33
|
return (
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
<CunninghamProvider>
|
|
35
|
+
<QueryClientProvider client={createTestQueryClient({ user: true })}>
|
|
36
|
+
<IntlProvider locale="en">
|
|
37
|
+
<SessionProvider>
|
|
38
|
+
<MemoryRouter>{children}</MemoryRouter>
|
|
39
|
+
</SessionProvider>
|
|
40
|
+
</IntlProvider>
|
|
41
|
+
</QueryClientProvider>
|
|
42
|
+
</CunninghamProvider>
|
|
40
43
|
);
|
|
41
44
|
};
|
|
42
45
|
|
|
@@ -64,6 +67,7 @@ describe('<SignContractButton/>', () => {
|
|
|
64
67
|
);
|
|
65
68
|
};
|
|
66
69
|
const order = CredentialOrderFactory({
|
|
70
|
+
state: OrderState.TO_SIGN,
|
|
67
71
|
contract: ContractFactory({ student_signed_on: undefined }).one(),
|
|
68
72
|
}).one();
|
|
69
73
|
fetchMock.get(
|
|
@@ -114,6 +118,7 @@ describe('<SignContractButton/>', () => {
|
|
|
114
118
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/orders/${order.id}/`, signedOrder, {
|
|
115
119
|
overwriteRoutes: true,
|
|
116
120
|
});
|
|
121
|
+
fetchMock.post(`https://joanie.endpoint/api/v1.0/signature/notifications/`, 200);
|
|
117
122
|
const $modal = screen.getByTestId('dashboard-contract-frame');
|
|
118
123
|
await user.click(await within($modal).findByRole('button', { name: 'Sign' }));
|
|
119
124
|
|
|
@@ -121,11 +126,11 @@ describe('<SignContractButton/>', () => {
|
|
|
121
126
|
expect(
|
|
122
127
|
await within($modal).findByRole('heading', { name: 'Congratulations!' }),
|
|
123
128
|
).toBeInTheDocument();
|
|
124
|
-
// Orders's cache validation shouln't have
|
|
129
|
+
// Orders's cache validation shouln't have closed the ContractFrame.
|
|
125
130
|
expect(screen.queryByTestId('dashboard-contract-frame')).toBeInTheDocument();
|
|
126
131
|
|
|
127
132
|
// Close modal.
|
|
128
|
-
const $closeButton = screen.getByRole('button', { name: '
|
|
133
|
+
const $closeButton = screen.getByRole('button', { name: 'close' });
|
|
129
134
|
await user.click($closeButton);
|
|
130
135
|
expect(screen.queryByTestId('dashboard-contract-frame')).not.toBeInTheDocument();
|
|
131
136
|
|
|
@@ -5,12 +5,13 @@ import { MemoryRouter } from 'react-router-dom';
|
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
7
7
|
import { faker } from '@faker-js/faker';
|
|
8
|
+
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
9
|
+
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
8
10
|
import {
|
|
9
11
|
ContractFactory,
|
|
10
12
|
CredentialOrderFactory,
|
|
11
13
|
NestedCredentialOrderFactory,
|
|
12
14
|
} from 'utils/test/factories/joanie';
|
|
13
|
-
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
14
15
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
15
16
|
import JoanieApiProvider from 'contexts/JoanieApiContext';
|
|
16
17
|
import { Contract, CredentialOrder, NestedCredentialOrder, OrderState } from 'types/Joanie';
|
|
@@ -57,13 +58,15 @@ describe('<SignContractButton/>', () => {
|
|
|
57
58
|
let order: CredentialOrder | NestedCredentialOrder;
|
|
58
59
|
const Wrapper = ({ children }: PropsWithChildren) => {
|
|
59
60
|
return (
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
<CunninghamProvider>
|
|
62
|
+
<QueryClientProvider client={createTestQueryClient({ user: true })}>
|
|
63
|
+
<IntlProvider locale="en">
|
|
64
|
+
<JoanieApiProvider>
|
|
65
|
+
<MemoryRouter>{children}</MemoryRouter>
|
|
66
|
+
</JoanieApiProvider>
|
|
67
|
+
</IntlProvider>
|
|
68
|
+
</QueryClientProvider>
|
|
69
|
+
</CunninghamProvider>
|
|
67
70
|
);
|
|
68
71
|
};
|
|
69
72
|
|
|
@@ -77,7 +80,7 @@ describe('<SignContractButton/>', () => {
|
|
|
77
80
|
testCase === TestCase.FROM_ORDER_WITH_CONTRACT
|
|
78
81
|
? ContractFactory({ student_signed_on: null }).one()
|
|
79
82
|
: undefined;
|
|
80
|
-
order = OrderFactory({ contract: orderContract }).one();
|
|
83
|
+
order = OrderFactory({ contract: orderContract, state: OrderState.TO_SIGN }).one();
|
|
81
84
|
contract = order.contract;
|
|
82
85
|
}
|
|
83
86
|
});
|
|
@@ -88,11 +91,10 @@ describe('<SignContractButton/>', () => {
|
|
|
88
91
|
<SignContractButton order={order} contract={contract} writable={false} />
|
|
89
92
|
</Wrapper>,
|
|
90
93
|
);
|
|
91
|
-
expect(screen.getByRole('link', { name: 'Sign' })).toBeInTheDocument();
|
|
92
94
|
|
|
95
|
+
expect(screen.getByRole('link', { name: 'Sign' })).toBeInTheDocument();
|
|
93
96
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
94
97
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
95
|
-
expect(document.querySelector('div.ReactModalPortal')).not.toBeInTheDocument();
|
|
96
98
|
});
|
|
97
99
|
|
|
98
100
|
it('should display a button that open ContractFrame modal', async () => {
|
|
@@ -101,9 +103,9 @@ describe('<SignContractButton/>', () => {
|
|
|
101
103
|
<SignContractButton order={order} contract={contract} writable={true} />
|
|
102
104
|
</Wrapper>,
|
|
103
105
|
);
|
|
106
|
+
|
|
104
107
|
const $signButton = screen.queryByRole('button', { name: 'Sign' });
|
|
105
108
|
expect($signButton).toBeInTheDocument();
|
|
106
|
-
expect(document.querySelector('div.ReactModalPortal')).toBeInTheDocument();
|
|
107
109
|
|
|
108
110
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
109
111
|
expect(screen.queryByRole('link', { name: 'Sign' })).not.toBeInTheDocument();
|
|
@@ -145,7 +147,6 @@ describe('<SignContractButton/>', () => {
|
|
|
145
147
|
<SignContractButton order={order} contract={contract} writable={false} />
|
|
146
148
|
</Wrapper>,
|
|
147
149
|
);
|
|
148
|
-
expect(document.querySelector('div.ReactModalPortal')).toBeInTheDocument();
|
|
149
150
|
|
|
150
151
|
expect(screen.queryByRole('link', { name: 'Sign' })).not.toBeInTheDocument();
|
|
151
152
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
@@ -164,8 +165,6 @@ describe('<SignContractButton/>', () => {
|
|
|
164
165
|
</Wrapper>,
|
|
165
166
|
);
|
|
166
167
|
|
|
167
|
-
expect(document.querySelector('div.ReactModalPortal')).toBeInTheDocument();
|
|
168
|
-
|
|
169
168
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
170
169
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
171
170
|
expect(screen.queryByRole('link', { name: 'Sign' })).not.toBeInTheDocument();
|
|
@@ -194,11 +193,10 @@ describe('<SignContractButton/>', () => {
|
|
|
194
193
|
<SignContractButton order={order} contract={contract} writable={false} />
|
|
195
194
|
</Wrapper>,
|
|
196
195
|
);
|
|
197
|
-
expect(screen.queryByRole('button', { name: 'Download' })).toBeInTheDocument();
|
|
198
|
-
expect(document.querySelector('div.ReactModalPortal')).toBeInTheDocument();
|
|
199
196
|
|
|
200
|
-
expect(screen.queryByRole('
|
|
197
|
+
expect(screen.queryByRole('button', { name: 'Download' })).toBeInTheDocument();
|
|
201
198
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
199
|
+
expect(screen.queryByRole('link', { name: 'Sign' })).not.toBeInTheDocument();
|
|
202
200
|
});
|
|
203
201
|
|
|
204
202
|
it('should display a button to download the training contract', async () => {
|
|
@@ -214,8 +212,6 @@ describe('<SignContractButton/>', () => {
|
|
|
214
212
|
);
|
|
215
213
|
|
|
216
214
|
expect(screen.queryByRole('button', { name: 'Download' })).toBeInTheDocument();
|
|
217
|
-
expect(document.querySelector('div.ReactModalPortal')).toBeInTheDocument();
|
|
218
|
-
|
|
219
215
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
220
216
|
expect(screen.queryByRole('link', { name: 'Sign' })).not.toBeInTheDocument();
|
|
221
217
|
});
|
|
@@ -67,7 +67,9 @@ const SignContractButton = ({ order, contract, writable, className }: SignContra
|
|
|
67
67
|
const [contractLoading, setContractLoading] = useState(false);
|
|
68
68
|
const contractState = ContractHelper.getState(contract);
|
|
69
69
|
const notReadyToSign =
|
|
70
|
-
|
|
70
|
+
![OrderState.TO_SIGN, OrderState.SIGNING].includes(order.state) ||
|
|
71
|
+
contractLoading ||
|
|
72
|
+
contractFrameOpened;
|
|
71
73
|
|
|
72
74
|
if (!writable && contractState === ContractState.UNSIGNED) {
|
|
73
75
|
return <SignContractButtonLink orderId={order.id} className={className} />;
|
|
@@ -9,6 +9,7 @@ import { useCreditCard, useCreditCards } from 'hooks/useCreditCards/index';
|
|
|
9
9
|
import { SessionProvider } from 'contexts/SessionContext';
|
|
10
10
|
import { Deferred } from 'utils/test/deferred';
|
|
11
11
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
12
|
+
import { HttpStatusCode } from 'utils/errors/HttpError';
|
|
12
13
|
import { CreditCard } from 'types/Joanie';
|
|
13
14
|
|
|
14
15
|
jest.mock('utils/context', () => ({
|
|
@@ -23,6 +24,7 @@ describe('useCreditCards', () => {
|
|
|
23
24
|
beforeEach(() => {
|
|
24
25
|
fetchMock.get('https://joanie.endpoint/api/v1.0/orders/', []);
|
|
25
26
|
fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', []);
|
|
27
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', []);
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
afterEach(() => {
|
|
@@ -43,7 +45,9 @@ describe('useCreditCards', () => {
|
|
|
43
45
|
it('retrieves all the credit cards', async () => {
|
|
44
46
|
const creditCards = CreditCardFactory().many(5);
|
|
45
47
|
const responseDeferred = new Deferred();
|
|
46
|
-
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', responseDeferred.promise
|
|
48
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', responseDeferred.promise, {
|
|
49
|
+
overwriteRoutes: true,
|
|
50
|
+
});
|
|
47
51
|
|
|
48
52
|
const { result } = renderHook(() => useCreditCards(), {
|
|
49
53
|
wrapper: Wrapper,
|
|
@@ -53,7 +57,6 @@ describe('useCreditCards', () => {
|
|
|
53
57
|
expect(result.current.states.fetching).toBe(true);
|
|
54
58
|
expect(result.current.items).toEqual([]);
|
|
55
59
|
});
|
|
56
|
-
expect(result.current.states.creating).toBe(false);
|
|
57
60
|
expect(result.current.states.deleting).toBe(false);
|
|
58
61
|
expect(result.current.states.updating).toBe(false);
|
|
59
62
|
expect(result.current.states.isPending).toBe(true);
|
|
@@ -67,7 +70,6 @@ describe('useCreditCards', () => {
|
|
|
67
70
|
expect(result.current.states.fetching).toBe(false);
|
|
68
71
|
expect(JSON.stringify(result.current.items)).toBe(JSON.stringify(creditCards));
|
|
69
72
|
});
|
|
70
|
-
expect(result.current.states.creating).toBe(false);
|
|
71
73
|
expect(result.current.states.deleting).toBe(false);
|
|
72
74
|
expect(result.current.states.updating).toBe(false);
|
|
73
75
|
expect(result.current.states.isPending).toBe(false);
|
|
@@ -78,7 +80,9 @@ describe('useCreditCards', () => {
|
|
|
78
80
|
const creditCards = CreditCardFactory().many(5);
|
|
79
81
|
const creditCard: CreditCard = creditCards[3];
|
|
80
82
|
const responseDeferred = new Deferred();
|
|
81
|
-
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', responseDeferred.promise
|
|
83
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', responseDeferred.promise, {
|
|
84
|
+
overwriteRoutes: true,
|
|
85
|
+
});
|
|
82
86
|
const { result } = renderHook(() => useCreditCard(creditCard.id), {
|
|
83
87
|
wrapper: Wrapper,
|
|
84
88
|
});
|
|
@@ -88,7 +92,6 @@ describe('useCreditCards', () => {
|
|
|
88
92
|
expect(result.current.item).toEqual(undefined);
|
|
89
93
|
});
|
|
90
94
|
|
|
91
|
-
expect(result.current.states.creating).toBe(false);
|
|
92
95
|
expect(result.current.states.deleting).toBe(false);
|
|
93
96
|
expect(result.current.states.updating).toBe(false);
|
|
94
97
|
expect(result.current.states.isPending).toBe(true);
|
|
@@ -103,10 +106,71 @@ describe('useCreditCards', () => {
|
|
|
103
106
|
expect(JSON.stringify(result.current.item)).toBe(JSON.stringify(creditCard));
|
|
104
107
|
});
|
|
105
108
|
|
|
106
|
-
expect(result.current.states.creating).toBe(false);
|
|
107
109
|
expect(result.current.states.deleting).toBe(false);
|
|
108
110
|
expect(result.current.states.updating).toBe(false);
|
|
109
111
|
expect(result.current.states.isPending).toBe(false);
|
|
110
112
|
expect(result.current.states.error).toBe(undefined);
|
|
111
113
|
});
|
|
114
|
+
|
|
115
|
+
it('tokenize a credit card', async () => {
|
|
116
|
+
const responseDeferred = new Deferred();
|
|
117
|
+
fetchMock.post(
|
|
118
|
+
'https://joanie.endpoint/api/v1.0/credit-cards/tokenize-card/',
|
|
119
|
+
responseDeferred.promise,
|
|
120
|
+
);
|
|
121
|
+
const { result } = renderHook(() => useCreditCards(undefined, { enabled: false }), {
|
|
122
|
+
wrapper: Wrapper,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(result.current).not.toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await act(async () => {
|
|
130
|
+
result.current.methods.tokenize();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(result.current.states.tokenizing).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.current.states.deleting).toBe(false);
|
|
138
|
+
expect(result.current.states.updating).toBe(false);
|
|
139
|
+
expect(result.current.states.fetching).toBe(false);
|
|
140
|
+
expect(result.current.states.isFetched).toBe(true);
|
|
141
|
+
expect(result.current.states.isPending).toBe(true);
|
|
142
|
+
expect(result.current.states.error).toBe(undefined);
|
|
143
|
+
|
|
144
|
+
await act(async () => {
|
|
145
|
+
responseDeferred.resolve({});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(result.current.states.tokenizing).toBe(false);
|
|
149
|
+
expect(result.current.states.isPending).toBe(false);
|
|
150
|
+
expect(result.current.states.error).toBe(undefined);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('manages error during credit card tokenization', async () => {
|
|
154
|
+
fetchMock.post(
|
|
155
|
+
'https://joanie.endpoint/api/v1.0/credit-cards/tokenize-card/',
|
|
156
|
+
HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
157
|
+
);
|
|
158
|
+
const { result } = renderHook(() => useCreditCards(undefined, { enabled: true }), {
|
|
159
|
+
wrapper: Wrapper,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(result.current).not.toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await act(async () => {
|
|
167
|
+
await expect(result.current.methods.tokenize()).rejects.toThrow('Internal Server Error');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(result.current.states.error).toBe(
|
|
171
|
+
'An error occurred while adding a credit card. Please retry later.',
|
|
172
|
+
);
|
|
173
|
+
expect(result.current.states.isPending).toBe(false);
|
|
174
|
+
expect(result.current.states.tokenizing).toBe(false);
|
|
175
|
+
});
|
|
112
176
|
});
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { defineMessages } from 'react-intl';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
2
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { API, CreditCard } from 'types/Joanie';
|
|
4
4
|
import { useJoanieApi } from 'contexts/JoanieApiContext';
|
|
5
|
-
import {
|
|
5
|
+
import { useSessionMutation } from 'utils/react-query/useSessionMutation';
|
|
6
|
+
import {
|
|
7
|
+
QueryOptions,
|
|
8
|
+
ResourcesQuery,
|
|
9
|
+
useResource,
|
|
10
|
+
useResourcesCustom,
|
|
11
|
+
UseResourcesProps,
|
|
12
|
+
} from '../useResources';
|
|
6
13
|
|
|
7
14
|
const messages = defineMessages({
|
|
8
15
|
errorUpdate: {
|
|
@@ -20,10 +27,10 @@ const messages = defineMessages({
|
|
|
20
27
|
description: 'Error message shown to the user when credit card deletion request fails.',
|
|
21
28
|
defaultMessage: 'An error occurred while deleting the credit card. Please retry later.',
|
|
22
29
|
},
|
|
23
|
-
|
|
24
|
-
id: 'hooks.useCreditCards.
|
|
25
|
-
description: 'Error message shown to the user when credit card
|
|
26
|
-
defaultMessage: 'An error occurred while
|
|
30
|
+
errorTokenize: {
|
|
31
|
+
id: 'hooks.useCreditCards.errorTokenize',
|
|
32
|
+
description: 'Error message shown to the user when credit card tokenize request fails.',
|
|
33
|
+
defaultMessage: 'An error occurred while adding a credit card. Please retry later.',
|
|
27
34
|
},
|
|
28
35
|
errorNotFound: {
|
|
29
36
|
id: 'hooks.useCreditCards.errorNotFound',
|
|
@@ -32,16 +39,47 @@ const messages = defineMessages({
|
|
|
32
39
|
},
|
|
33
40
|
});
|
|
34
41
|
|
|
42
|
+
const useCreditCardResources =
|
|
43
|
+
(props: UseResourcesProps<CreditCard, ResourcesQuery, API['user']['creditCards']>) =>
|
|
44
|
+
(filters?: ResourcesQuery, queryOptions?: QueryOptions<CreditCard>) => {
|
|
45
|
+
const custom = useResourcesCustom({ ...props, filters, queryOptions });
|
|
46
|
+
const queryClient = useQueryClient();
|
|
47
|
+
const intl = useIntl();
|
|
48
|
+
const api = props.apiInterface();
|
|
49
|
+
const mutation = (props.session ? useSessionMutation : useMutation) as typeof useMutation;
|
|
50
|
+
const tokenizeHandler = mutation({
|
|
51
|
+
mutationFn: api.tokenize,
|
|
52
|
+
onSuccess: async () => {
|
|
53
|
+
custom.methods.setError(undefined);
|
|
54
|
+
props.onMutationSuccess?.(queryClient);
|
|
55
|
+
},
|
|
56
|
+
onError: () => custom.methods.setError(intl.formatMessage(messages.errorTokenize)),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
...custom,
|
|
61
|
+
methods: {
|
|
62
|
+
...custom.methods,
|
|
63
|
+
tokenize: tokenizeHandler.mutateAsync,
|
|
64
|
+
},
|
|
65
|
+
states: {
|
|
66
|
+
...custom.states,
|
|
67
|
+
isPending: [tokenizeHandler, custom.states].some((value) => value?.isPending),
|
|
68
|
+
tokenizing: tokenizeHandler.isPending,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
35
73
|
/**
|
|
36
74
|
* Joanie Api hook to retrieve/create/update/delete credit cards
|
|
37
75
|
* owned by the authenticated user.
|
|
38
76
|
*/
|
|
39
|
-
const props: UseResourcesProps<CreditCard> = {
|
|
77
|
+
const props: UseResourcesProps<CreditCard, ResourcesQuery, API['user']['creditCards']> = {
|
|
40
78
|
queryKey: ['creditCards'],
|
|
41
79
|
apiInterface: () => useJoanieApi().user.creditCards,
|
|
42
80
|
omniscient: true,
|
|
43
81
|
session: true,
|
|
44
82
|
messages,
|
|
45
83
|
};
|
|
46
|
-
export const useCreditCards =
|
|
47
|
-
export const useCreditCard = useResource
|
|
84
|
+
export const useCreditCards = useCreditCardResources(props);
|
|
85
|
+
export const useCreditCard = useResource(props);
|