richie-education 3.2.2-dev45 → 3.2.2-dev56
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/i18n/locales/ar-SA.json +20 -0
- package/i18n/locales/es-ES.json +20 -0
- package/i18n/locales/fa-IR.json +20 -0
- package/i18n/locales/fr-CA.json +20 -0
- package/i18n/locales/fr-FR.json +25 -5
- package/i18n/locales/ko-KR.json +20 -0
- package/i18n/locales/pt-PT.json +20 -0
- package/i18n/locales/ru-RU.json +20 -0
- package/i18n/locales/vi-VN.json +20 -0
- package/js/api/auth/keycloak.spec.ts +63 -2
- package/js/api/auth/keycloak.ts +40 -2
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +3 -1
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +85 -4
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +26 -20
- package/js/components/SaleTunnel/_styles.scss +8 -0
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +209 -53
- package/js/components/SaleTunnel/index.spec.tsx +161 -0
- package/js/hooks/useOpenEdxProfile/index.ts +4 -2
- package/js/index.tsx +1 -1
- package/js/translations/ar-SA.json +1 -1
- package/js/translations/es-ES.json +1 -1
- package/js/translations/fa-IR.json +1 -1
- package/js/translations/fr-CA.json +1 -1
- package/js/translations/fr-FR.json +1 -1
- package/js/translations/ko-KR.json +1 -1
- package/js/translations/pt-PT.json +1 -1
- package/js/translations/ru-RU.json +1 -1
- package/js/translations/vi-VN.json +1 -1
- package/js/types/api.ts +14 -5
- package/js/types/keycloak.ts +8 -0
- package/js/widgets/index.tsx +3 -2
- package/package.json +1 -1
- package/scss/colors/_theme.scss +1 -0
|
@@ -11,6 +11,9 @@ import WithdrawRightCheckbox from 'components/SaleTunnel/WithdrawRightCheckbox';
|
|
|
11
11
|
import { PaymentSchedule, ProductType } from 'types/Joanie';
|
|
12
12
|
import { usePaymentPlan } from 'hooks/usePaymentPlan';
|
|
13
13
|
import { HttpError } from 'utils/errors/HttpError';
|
|
14
|
+
import { APIBackend, KeycloakAccountApi } from 'types/api';
|
|
15
|
+
import context from 'utils/context';
|
|
16
|
+
import { AuthenticationApi } from 'api/authentication';
|
|
14
17
|
|
|
15
18
|
const messages = defineMessages({
|
|
16
19
|
title: {
|
|
@@ -49,6 +52,31 @@ const messages = defineMessages({
|
|
|
49
52
|
defaultMessage:
|
|
50
53
|
'This email will be used to send you confirmation mails, it is the one you created your account with.',
|
|
51
54
|
},
|
|
55
|
+
keycloakUsernameLabel: {
|
|
56
|
+
id: 'components.SaleTunnel.Information.keycloak.account.label',
|
|
57
|
+
description: 'Label for the name',
|
|
58
|
+
defaultMessage: 'Account name',
|
|
59
|
+
},
|
|
60
|
+
keycloakUsernameInfo: {
|
|
61
|
+
id: 'components.SaleTunnel.Information.keycloak.account.info',
|
|
62
|
+
description: 'Info for the name',
|
|
63
|
+
defaultMessage: 'This name will be used in legal documents.',
|
|
64
|
+
},
|
|
65
|
+
keycloakEmailInfo: {
|
|
66
|
+
id: 'components.SaleTunnel.Information.keycloak.email.info',
|
|
67
|
+
description: 'Info for the email',
|
|
68
|
+
defaultMessage: 'This email will be used to send you confirmation mails.',
|
|
69
|
+
},
|
|
70
|
+
keycloakAccountLinkInfo: {
|
|
71
|
+
id: 'components.SaleTunnel.Information.keycloak.updateLinkInfo',
|
|
72
|
+
description: 'Text before the keycloak account update link',
|
|
73
|
+
defaultMessage: 'If any of the information above is incorrect,',
|
|
74
|
+
},
|
|
75
|
+
keycloakAccountLinkLabel: {
|
|
76
|
+
id: 'components.SaleTunnel.Information.keycloak.updateLinkLabel',
|
|
77
|
+
description: 'Label of the keycloak link to update account',
|
|
78
|
+
defaultMessage: 'please update your account',
|
|
79
|
+
},
|
|
52
80
|
voucherTitle: {
|
|
53
81
|
id: 'components.SaleTunnel.Information.voucher.title',
|
|
54
82
|
description: 'Title for the voucher',
|
|
@@ -130,6 +158,8 @@ export const SaleTunnelInformationSingular = () => {
|
|
|
130
158
|
setNeedsPayment(!fromBatchOrder);
|
|
131
159
|
}, [fromBatchOrder, setNeedsPayment]);
|
|
132
160
|
|
|
161
|
+
const isKeycloakBackend = context?.authentication.backend === APIBackend.KEYCLOAK;
|
|
162
|
+
|
|
133
163
|
return (
|
|
134
164
|
<>
|
|
135
165
|
<div>
|
|
@@ -148,11 +178,17 @@ export const SaleTunnelInformationSingular = () => {
|
|
|
148
178
|
<div className="description mb-s">
|
|
149
179
|
<FormattedMessage {...messages.description} />
|
|
150
180
|
</div>
|
|
151
|
-
<OpenEdxFullNameForm />
|
|
152
181
|
<AddressSelector />
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
182
|
+
{isKeycloakBackend ? (
|
|
183
|
+
<KeycloakAccountEdit />
|
|
184
|
+
) : (
|
|
185
|
+
<>
|
|
186
|
+
<OpenEdxFullNameForm />
|
|
187
|
+
<div className="mt-s">
|
|
188
|
+
<Email />
|
|
189
|
+
</div>
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
156
192
|
</div>
|
|
157
193
|
)}
|
|
158
194
|
<div>
|
|
@@ -163,6 +199,51 @@ export const SaleTunnelInformationSingular = () => {
|
|
|
163
199
|
);
|
|
164
200
|
};
|
|
165
201
|
|
|
202
|
+
const KeycloakAccountEdit = () => {
|
|
203
|
+
const accountApi = AuthenticationApi!.account as KeycloakAccountApi;
|
|
204
|
+
const { user } = useSession();
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<>
|
|
208
|
+
<div className="mt-s">
|
|
209
|
+
<div className="sale-tunnel__username">
|
|
210
|
+
<div className="sale-tunnel__username__top">
|
|
211
|
+
<h4>
|
|
212
|
+
<FormattedMessage {...messages.keycloakUsernameLabel} />
|
|
213
|
+
</h4>
|
|
214
|
+
<div className="fw-bold">{user?.username}</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div className="sale-tunnel__username__description">
|
|
217
|
+
<FormattedMessage {...messages.keycloakUsernameInfo} />
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div className="mt-s">
|
|
222
|
+
<div className="sale-tunnel__email">
|
|
223
|
+
<div className="sale-tunnel__email__top">
|
|
224
|
+
<h4>
|
|
225
|
+
<FormattedMessage {...messages.emailLabel} />
|
|
226
|
+
</h4>
|
|
227
|
+
<div className="fw-bold">{user?.email}</div>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="sale-tunnel__email__description">
|
|
230
|
+
<FormattedMessage {...messages.keycloakEmailInfo} />
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
<div className="mt-s">
|
|
235
|
+
<div className="sale-tunnel__account-link">
|
|
236
|
+
<FormattedMessage {...messages.keycloakAccountLinkInfo} />{' '}
|
|
237
|
+
<a href={accountApi.updateUrl()}>
|
|
238
|
+
<FormattedMessage {...messages.keycloakAccountLinkLabel} />
|
|
239
|
+
</a>
|
|
240
|
+
.
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
166
247
|
const Email = () => {
|
|
167
248
|
const { user } = useSession();
|
|
168
249
|
const { data: openEdxProfileData } = useOpenEdxProfile({
|
|
@@ -4,6 +4,7 @@ import { Select } from '@openfun/cunningham-react';
|
|
|
4
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
5
5
|
import { SaleTunnelInformationSingular } from 'components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular';
|
|
6
6
|
import { SaleTunnelInformationGroup } from 'components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup';
|
|
7
|
+
import { ProductType } from 'types/Joanie';
|
|
7
8
|
|
|
8
9
|
const messages = defineMessages({
|
|
9
10
|
purchaseTypeTitle: {
|
|
@@ -35,7 +36,8 @@ export enum FormType {
|
|
|
35
36
|
|
|
36
37
|
export const SaleTunnelInformation = () => {
|
|
37
38
|
const intl = useIntl();
|
|
38
|
-
const { setBatchOrder, setSchedule } = useSaleTunnelContext();
|
|
39
|
+
const { setBatchOrder, setSchedule, product } = useSaleTunnelContext();
|
|
40
|
+
const productType = product.type;
|
|
39
41
|
const options = [
|
|
40
42
|
{ label: intl.formatMessage(messages.purchaseTypeOptionSingle), value: FormType.SINGULAR },
|
|
41
43
|
{ label: intl.formatMessage(messages.purchaseTypeOptionGroup), value: FormType.GROUP },
|
|
@@ -44,25 +46,29 @@ export const SaleTunnelInformation = () => {
|
|
|
44
46
|
|
|
45
47
|
return (
|
|
46
48
|
<div className="sale-tunnel__main__column sale-tunnel__information">
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
49
|
+
{productType === ProductType.CERTIFICATE ? (
|
|
50
|
+
<SaleTunnelInformationSingular />
|
|
51
|
+
) : (
|
|
52
|
+
<div>
|
|
53
|
+
<h3 className="block-title mb-t">
|
|
54
|
+
<FormattedMessage {...messages.purchaseTypeTitle} />
|
|
55
|
+
</h3>
|
|
56
|
+
<Select
|
|
57
|
+
label={intl.formatMessage(messages.purchaseTypeSelect)}
|
|
58
|
+
options={options}
|
|
59
|
+
fullWidth
|
|
60
|
+
value={purchaseType}
|
|
61
|
+
clearable={false}
|
|
62
|
+
onChange={(e) => {
|
|
63
|
+
setPurchaseType(e.target.value as FormType);
|
|
64
|
+
setBatchOrder(undefined);
|
|
65
|
+
setSchedule(undefined);
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
{purchaseType === FormType.SINGULAR && <SaleTunnelInformationSingular />}
|
|
69
|
+
{purchaseType === FormType.GROUP && <SaleTunnelInformationGroup />}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
66
72
|
</div>
|
|
67
73
|
);
|
|
68
74
|
};
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
&__username,
|
|
70
71
|
&__email {
|
|
71
72
|
&__top {
|
|
72
73
|
display: flex;
|
|
@@ -82,6 +83,13 @@
|
|
|
82
83
|
font-size: 0.75rem;
|
|
83
84
|
}
|
|
84
85
|
}
|
|
86
|
+
|
|
87
|
+
&__account-link {
|
|
88
|
+
a {
|
|
89
|
+
color: r-theme-val(sale-tunnel, account-link-color);
|
|
90
|
+
text-decoration: underline;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
85
93
|
.price--striked {
|
|
86
94
|
text-decoration: line-through;
|
|
87
95
|
opacity: 0.5;
|
|
@@ -46,6 +46,44 @@ jest.mock('utils/indirection/window', () => ({
|
|
|
46
46
|
|
|
47
47
|
jest.mock('../PaymentInterfaces');
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Setup common mocks for B2B batch order tests
|
|
51
|
+
*/
|
|
52
|
+
const setupBatchOrderMocks = (params: {
|
|
53
|
+
course: any;
|
|
54
|
+
product: any;
|
|
55
|
+
offering: any;
|
|
56
|
+
paymentPlan: any;
|
|
57
|
+
organizations?: any[] | any;
|
|
58
|
+
}) => {
|
|
59
|
+
const { course, product, offering, paymentPlan, organizations } = params;
|
|
60
|
+
|
|
61
|
+
fetchMock.get(
|
|
62
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
|
|
63
|
+
offering,
|
|
64
|
+
);
|
|
65
|
+
fetchMock.get(
|
|
66
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
67
|
+
paymentPlan,
|
|
68
|
+
);
|
|
69
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/enrollments/`, []);
|
|
70
|
+
fetchMock.get(
|
|
71
|
+
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify({
|
|
72
|
+
product_id: product.id,
|
|
73
|
+
course_code: course.code,
|
|
74
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
75
|
+
})}`,
|
|
76
|
+
[],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (organizations) {
|
|
80
|
+
fetchMock.get(
|
|
81
|
+
`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/get-organizations/`,
|
|
82
|
+
organizations,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
49
87
|
describe('SaleTunnel', () => {
|
|
50
88
|
let richieUser: User;
|
|
51
89
|
let openApiEdxProfile: OpenEdxApiProfile;
|
|
@@ -86,27 +124,7 @@ describe('SaleTunnel', () => {
|
|
|
86
124
|
const paymentPlan = PaymentPlanFactory().one();
|
|
87
125
|
const organizations = OrganizationFactory().many(3);
|
|
88
126
|
|
|
89
|
-
|
|
90
|
-
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
|
|
91
|
-
offering,
|
|
92
|
-
);
|
|
93
|
-
fetchMock.get(
|
|
94
|
-
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
95
|
-
paymentPlan,
|
|
96
|
-
);
|
|
97
|
-
fetchMock.get(`https://joanie.endpoint/api/v1.0/enrollments/`, []);
|
|
98
|
-
fetchMock.get(
|
|
99
|
-
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify({
|
|
100
|
-
product_id: product.id,
|
|
101
|
-
course_code: course.code,
|
|
102
|
-
state: NOT_CANCELED_ORDER_STATES,
|
|
103
|
-
})}`,
|
|
104
|
-
[],
|
|
105
|
-
);
|
|
106
|
-
fetchMock.get(
|
|
107
|
-
`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/get-organizations/`,
|
|
108
|
-
organizations,
|
|
109
|
-
);
|
|
127
|
+
setupBatchOrderMocks({ course, product, offering, paymentPlan, organizations });
|
|
110
128
|
|
|
111
129
|
render(<CourseProductItem productId={product.id} course={course} />, {
|
|
112
130
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
@@ -304,24 +322,13 @@ describe('SaleTunnel', () => {
|
|
|
304
322
|
}).one();
|
|
305
323
|
const paymentPlan = PaymentPlanFactory().one();
|
|
306
324
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
offering,
|
|
310
|
-
);
|
|
311
|
-
fetchMock.get(
|
|
312
|
-
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
313
|
-
paymentPlan,
|
|
314
|
-
);
|
|
315
|
-
fetchMock.get(`https://joanie.endpoint/api/v1.0/enrollments/`, []);
|
|
325
|
+
setupBatchOrderMocks({ course, product, offering, paymentPlan });
|
|
326
|
+
|
|
316
327
|
const orderQueryParameters = {
|
|
317
328
|
product_id: product.id,
|
|
318
329
|
course_code: course.code,
|
|
319
330
|
state: NOT_CANCELED_ORDER_STATES,
|
|
320
331
|
};
|
|
321
|
-
fetchMock.get(
|
|
322
|
-
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
|
|
323
|
-
[],
|
|
324
|
-
);
|
|
325
332
|
|
|
326
333
|
render(<CourseProductItem productId={product.id} course={course} />, {
|
|
327
334
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
@@ -423,27 +430,13 @@ describe('SaleTunnel', () => {
|
|
|
423
430
|
product: { id: product.id, title: product.title },
|
|
424
431
|
}).one();
|
|
425
432
|
|
|
426
|
-
|
|
427
|
-
|
|
433
|
+
setupBatchOrderMocks({
|
|
434
|
+
course,
|
|
435
|
+
product,
|
|
428
436
|
offering,
|
|
429
|
-
);
|
|
430
|
-
fetchMock.get(
|
|
431
|
-
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
432
437
|
paymentPlan,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
fetchMock.get(
|
|
436
|
-
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify({
|
|
437
|
-
product_id: product.id,
|
|
438
|
-
course_code: course.code,
|
|
439
|
-
state: NOT_CANCELED_ORDER_STATES,
|
|
440
|
-
})}`,
|
|
441
|
-
[],
|
|
442
|
-
);
|
|
443
|
-
fetchMock.get(
|
|
444
|
-
`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/get-organizations/`,
|
|
445
|
-
offeringOrganization,
|
|
446
|
-
);
|
|
438
|
+
organizations: offeringOrganization,
|
|
439
|
+
});
|
|
447
440
|
|
|
448
441
|
render(<CourseProductItem productId={product.id} course={course} />, {
|
|
449
442
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
@@ -573,4 +566,167 @@ describe('SaleTunnel', () => {
|
|
|
573
566
|
'Unable to create the order: the maximum number of available seats for this offering has been reached. Please contact support for more information.',
|
|
574
567
|
);
|
|
575
568
|
}, 30000);
|
|
569
|
+
|
|
570
|
+
it('tests optional fields can be filled and cleared without breaking validation', async () => {
|
|
571
|
+
const course = PacedCourseFactory().one();
|
|
572
|
+
const product = ProductFactory().one();
|
|
573
|
+
const offering = OfferingFactory({ course, product, is_withdrawable: false }).one();
|
|
574
|
+
const paymentPlan = PaymentPlanFactory().one();
|
|
575
|
+
const organizations = OrganizationFactory().many(3);
|
|
576
|
+
|
|
577
|
+
setupBatchOrderMocks({ course, product, offering, paymentPlan, organizations });
|
|
578
|
+
|
|
579
|
+
render(<CourseProductItem productId={product.id} course={course} />, {
|
|
580
|
+
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
await screen.findByRole('heading', { level: 3, name: product.title });
|
|
584
|
+
const user = userEvent.setup();
|
|
585
|
+
const buyButton = screen.getByRole('button', { name: product.call_to_action });
|
|
586
|
+
await user.click(buyButton);
|
|
587
|
+
await screen.findByTestId('generic-sale-tunnel-payment-step');
|
|
588
|
+
|
|
589
|
+
// Select group buy form
|
|
590
|
+
const formTypeSelect = screen.getByRole('combobox', { name: 'Purchase type' });
|
|
591
|
+
await user.click(formTypeSelect);
|
|
592
|
+
await user.click(screen.getByText('I am purchasing on behalf of an organization'));
|
|
593
|
+
|
|
594
|
+
// Company step
|
|
595
|
+
const $companyName = await screen.findByRole('textbox', { name: 'Company name' });
|
|
596
|
+
const $idNumber = screen.getByRole('textbox', { name: /Registration number/ });
|
|
597
|
+
const $address = screen.getByRole('textbox', { name: 'Address' });
|
|
598
|
+
const $postCode = screen.getByRole('textbox', { name: 'Postal code' });
|
|
599
|
+
const $city = screen.getByRole('textbox', { name: 'City' });
|
|
600
|
+
const $country = screen.getByRole('combobox', { name: 'Country' });
|
|
601
|
+
|
|
602
|
+
await user.type($companyName, 'Test Company');
|
|
603
|
+
await user.type($idNumber, '123456789');
|
|
604
|
+
await user.type($address, '123 Test Street');
|
|
605
|
+
await user.type($postCode, '12345');
|
|
606
|
+
await user.type($city, 'Test City');
|
|
607
|
+
await user.click($country);
|
|
608
|
+
await user.click(screen.getByText('France'));
|
|
609
|
+
|
|
610
|
+
// Fill and clear billing address optional fields
|
|
611
|
+
const $billingCheckbox = screen.getByLabelText('Use different billing information');
|
|
612
|
+
await user.click($billingCheckbox);
|
|
613
|
+
|
|
614
|
+
const $billingContactName = await screen.findByRole('textbox', {
|
|
615
|
+
name: 'Contact name',
|
|
616
|
+
});
|
|
617
|
+
const $billingContactEmail = screen.getByRole('textbox', { name: 'Contact email' });
|
|
618
|
+
const billingCompanyInputs = screen.getAllByRole('textbox', { name: 'Company name' });
|
|
619
|
+
const $billingCompanyName = billingCompanyInputs[billingCompanyInputs.length - 1];
|
|
620
|
+
const billingIdInputs = screen.getAllByRole('textbox', { name: /Registration number/ });
|
|
621
|
+
const $billingIdNumber = billingIdInputs[billingIdInputs.length - 1];
|
|
622
|
+
const billingAddressInputs = screen.getAllByRole('textbox', { name: 'Address' });
|
|
623
|
+
const $billingAddress = billingAddressInputs[billingAddressInputs.length - 1];
|
|
624
|
+
const billingPostCodeInputs = screen.getAllByRole('textbox', { name: 'Postal code' });
|
|
625
|
+
const $billingPostCode = billingPostCodeInputs[billingPostCodeInputs.length - 1];
|
|
626
|
+
const billingCityInputs = screen.getAllByRole('textbox', { name: 'City' });
|
|
627
|
+
const $billingCity = billingCityInputs[billingCityInputs.length - 1];
|
|
628
|
+
const billingCountrySelects = screen.getAllByRole('combobox', { name: 'Country' });
|
|
629
|
+
const $billingCountry = billingCountrySelects[billingCountrySelects.length - 1];
|
|
630
|
+
|
|
631
|
+
await user.type($billingContactName, 'Billing Contact');
|
|
632
|
+
await user.type($billingContactEmail, 'billing@test.com');
|
|
633
|
+
await user.type($billingCompanyName, 'Billing Company');
|
|
634
|
+
await user.type($billingIdNumber, '987654321');
|
|
635
|
+
await user.type($billingAddress, '456 Billing Street');
|
|
636
|
+
await user.type($billingPostCode, '54321');
|
|
637
|
+
await user.type($billingCity, 'Billing City');
|
|
638
|
+
await user.click($billingCountry);
|
|
639
|
+
await user.click(screen.getAllByText('France')[1]);
|
|
640
|
+
|
|
641
|
+
await user.clear($billingContactName);
|
|
642
|
+
await user.clear($billingContactEmail);
|
|
643
|
+
await user.clear($billingCompanyName);
|
|
644
|
+
await user.clear($billingIdNumber);
|
|
645
|
+
await user.clear($billingAddress);
|
|
646
|
+
await user.clear($billingPostCode);
|
|
647
|
+
await user.clear($billingCity);
|
|
648
|
+
|
|
649
|
+
await user.click($billingCheckbox);
|
|
650
|
+
|
|
651
|
+
// Follow-up step
|
|
652
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
653
|
+
const $lastName = await screen.findByRole('textbox', { name: 'Last name' });
|
|
654
|
+
const $firstName = screen.getByRole('textbox', { name: 'First name' });
|
|
655
|
+
const $role = screen.getByRole('textbox', { name: 'Role' });
|
|
656
|
+
const $email = screen.getByRole('textbox', { name: 'Email' });
|
|
657
|
+
const $phone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
658
|
+
|
|
659
|
+
await user.type($lastName, 'Doe');
|
|
660
|
+
await user.type($firstName, 'Jane');
|
|
661
|
+
await user.type($role, 'Manager');
|
|
662
|
+
await user.type($email, 'jane.doe@test.com');
|
|
663
|
+
await user.type($phone, '+33123456789');
|
|
664
|
+
|
|
665
|
+
// Signatory step
|
|
666
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
667
|
+
const $signatoryLastName = await screen.findByRole('textbox', { name: 'Last name' });
|
|
668
|
+
const $signatoryFirstName = screen.getByRole('textbox', { name: 'First name' });
|
|
669
|
+
const $signatoryRole = screen.getByRole('textbox', { name: 'Role' });
|
|
670
|
+
const $signatoryEmail = screen.getByRole('textbox', { name: 'Email' });
|
|
671
|
+
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
672
|
+
|
|
673
|
+
await user.type($signatoryLastName, 'Smith');
|
|
674
|
+
await user.type($signatoryFirstName, 'John');
|
|
675
|
+
await user.type($signatoryRole, 'Director');
|
|
676
|
+
await user.type($signatoryEmail, 'john.smith@test.com');
|
|
677
|
+
await user.type($signatoryPhone, '+33987654321');
|
|
678
|
+
|
|
679
|
+
// Participants step
|
|
680
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
681
|
+
const $nbParticipants = await screen.findByLabelText('Number of participants to register');
|
|
682
|
+
await user.type($nbParticipants, '10');
|
|
683
|
+
|
|
684
|
+
// Financing step - fill and clear optional fields
|
|
685
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
686
|
+
const $purchaseOrderRadio = await screen.findByLabelText('Purchase order');
|
|
687
|
+
await user.click($purchaseOrderRadio);
|
|
688
|
+
|
|
689
|
+
const $fundingEntity = screen.getByLabelText('Entity name');
|
|
690
|
+
await user.type($fundingEntity, 'Test OPCO');
|
|
691
|
+
expect($fundingEntity).toHaveValue('Test OPCO');
|
|
692
|
+
|
|
693
|
+
const $fundingAmount = screen.getByLabelText('Amount covered');
|
|
694
|
+
await user.type($fundingAmount, '1000');
|
|
695
|
+
expect($fundingAmount).toHaveValue(1000);
|
|
696
|
+
|
|
697
|
+
await user.clear($fundingAmount);
|
|
698
|
+
expect($fundingAmount).toHaveValue(null);
|
|
699
|
+
|
|
700
|
+
await user.clear($fundingEntity);
|
|
701
|
+
expect($fundingEntity).toHaveValue('');
|
|
702
|
+
|
|
703
|
+
// Submit the batch order
|
|
704
|
+
const batchOrderRead = BatchOrderReadFactory().one();
|
|
705
|
+
fetchMock.post('https://joanie.endpoint/api/v1.0/batch-orders/', batchOrderRead);
|
|
706
|
+
const $subscribeButton = screen.getByRole('button', {
|
|
707
|
+
name: 'Subscribe',
|
|
708
|
+
}) as HTMLButtonElement;
|
|
709
|
+
|
|
710
|
+
expect($subscribeButton).not.toBeDisabled();
|
|
711
|
+
await user.click($subscribeButton);
|
|
712
|
+
await screen.findByTestId('generic-sale-tunnel-success-step');
|
|
713
|
+
|
|
714
|
+
// Verify the batch order payload does NOT contain the cleared optional fields
|
|
715
|
+
const batchOrderCalls = fetchMock.calls('https://joanie.endpoint/api/v1.0/batch-orders/');
|
|
716
|
+
expect(batchOrderCalls).toHaveLength(1);
|
|
717
|
+
const batchOrderPayload = JSON.parse(batchOrderCalls[0][1]?.body as string);
|
|
718
|
+
|
|
719
|
+
expect(batchOrderPayload.billing_address).toBeUndefined();
|
|
720
|
+
expect(batchOrderPayload.funding_entity).toBeUndefined();
|
|
721
|
+
expect(batchOrderPayload.funding_amount).toBeUndefined();
|
|
722
|
+
expect(batchOrderPayload.organization_id).toBeUndefined();
|
|
723
|
+
|
|
724
|
+
expect(batchOrderPayload).toMatchObject({
|
|
725
|
+
offering_id: offering.id,
|
|
726
|
+
company_name: 'Test Company',
|
|
727
|
+
identification_number: '123456789',
|
|
728
|
+
payment_method: PaymentMethod.PURCHASE_ORDER,
|
|
729
|
+
nb_seats: '10',
|
|
730
|
+
});
|
|
731
|
+
}, 30000);
|
|
576
732
|
});
|