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.
Files changed (33) hide show
  1. package/i18n/locales/ar-SA.json +20 -0
  2. package/i18n/locales/es-ES.json +20 -0
  3. package/i18n/locales/fa-IR.json +20 -0
  4. package/i18n/locales/fr-CA.json +20 -0
  5. package/i18n/locales/fr-FR.json +25 -5
  6. package/i18n/locales/ko-KR.json +20 -0
  7. package/i18n/locales/pt-PT.json +20 -0
  8. package/i18n/locales/ru-RU.json +20 -0
  9. package/i18n/locales/vi-VN.json +20 -0
  10. package/js/api/auth/keycloak.spec.ts +63 -2
  11. package/js/api/auth/keycloak.ts +40 -2
  12. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +3 -1
  13. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +85 -4
  14. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +26 -20
  15. package/js/components/SaleTunnel/_styles.scss +8 -0
  16. package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +209 -53
  17. package/js/components/SaleTunnel/index.spec.tsx +161 -0
  18. package/js/hooks/useOpenEdxProfile/index.ts +4 -2
  19. package/js/index.tsx +1 -1
  20. package/js/translations/ar-SA.json +1 -1
  21. package/js/translations/es-ES.json +1 -1
  22. package/js/translations/fa-IR.json +1 -1
  23. package/js/translations/fr-CA.json +1 -1
  24. package/js/translations/fr-FR.json +1 -1
  25. package/js/translations/ko-KR.json +1 -1
  26. package/js/translations/pt-PT.json +1 -1
  27. package/js/translations/ru-RU.json +1 -1
  28. package/js/translations/vi-VN.json +1 -1
  29. package/js/types/api.ts +14 -5
  30. package/js/types/keycloak.ts +8 -0
  31. package/js/widgets/index.tsx +3 -2
  32. package/package.json +1 -1
  33. 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
- <div className="mt-s">
154
- <Email />
155
- </div>
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
- <div>
48
- <h3 className="block-title mb-t">
49
- <FormattedMessage {...messages.purchaseTypeTitle} />
50
- </h3>
51
- <Select
52
- label={intl.formatMessage(messages.purchaseTypeSelect)}
53
- options={options}
54
- fullWidth
55
- value={purchaseType}
56
- clearable={false}
57
- onChange={(e) => {
58
- setPurchaseType(e.target.value as FormType);
59
- setBatchOrder(undefined);
60
- setSchedule(undefined);
61
- }}
62
- />
63
- </div>
64
- {purchaseType === FormType.SINGULAR && <SaleTunnelInformationSingular />}
65
- {purchaseType === FormType.GROUP && <SaleTunnelInformationGroup />}
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
- fetchMock.get(
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
- fetchMock.get(
308
- `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
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
- fetchMock.get(
427
- `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
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
- fetchMock.get(`https://joanie.endpoint/api/v1.0/enrollments/`, []);
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
  });