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.
Files changed (82) hide show
  1. package/js/api/joanie.ts +12 -16
  2. package/js/api/lms/dummy.ts +1 -12
  3. package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
  4. package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
  5. package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
  6. package/js/components/ContractFrame/_styles.scss +6 -14
  7. package/js/components/CreditCardSelector/index.spec.tsx +7 -7
  8. package/js/components/CreditCardSelector/index.tsx +2 -2
  9. package/js/components/DownloadContractButton/index.spec.tsx +1 -1
  10. package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
  11. package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
  12. package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
  13. package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
  14. package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
  15. package/js/components/PaymentInterfaces/types.ts +5 -2
  16. package/js/components/PurchaseButton/index.spec.tsx +69 -37
  17. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
  18. package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
  19. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
  20. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +75 -41
  21. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +0 -30
  22. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
  23. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
  24. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
  25. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +5 -0
  26. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
  27. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
  28. package/js/components/SaleTunnel/_styles.scss +10 -1
  29. package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
  30. package/js/components/SaleTunnel/index.credential.spec.tsx +12 -21
  31. package/js/components/SaleTunnel/index.full-process.spec.tsx +110 -48
  32. package/js/components/SaleTunnel/index.spec.tsx +330 -779
  33. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
  34. package/js/components/SignContractButton/index.spec.tsx +16 -20
  35. package/js/components/SignContractButton/index.tsx +3 -1
  36. package/js/hooks/useCreditCards/index.spec.tsx +70 -6
  37. package/js/hooks/useCreditCards/index.ts +49 -11
  38. package/js/hooks/useOrders/index.spec.tsx +322 -0
  39. package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
  40. package/js/hooks/useProductOrder/index.spec.tsx +77 -60
  41. package/js/hooks/useProductOrder/index.tsx +2 -2
  42. package/js/hooks/useResources/useResourcesRoot.ts +1 -0
  43. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
  44. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
  45. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
  46. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
  47. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
  48. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
  49. package/js/settings/settings.test.ts +11 -2
  50. package/js/types/Joanie.ts +49 -34
  51. package/js/utils/OrderHelper/index.ts +38 -42
  52. package/js/utils/test/factories/joanie.ts +36 -51
  53. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
  54. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
  55. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
  56. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +7 -6
  57. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +9 -10
  58. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
  59. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
  60. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +28 -8
  61. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +2 -5
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +34 -35
  64. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +27 -24
  65. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
  66. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
  67. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
  68. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
  71. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
  72. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
  73. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
  74. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
  76. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
  77. package/package.json +1 -1
  78. package/scss/components/_index.scss +2 -1
  79. package/js/components/PaymentButton/_styles.scss +0 -27
  80. package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -333
  81. package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
  82. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
@@ -0,0 +1,229 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import { act, screen } from '@testing-library/react';
3
+ import fetchMock from 'fetch-mock';
4
+ import { userEvent } from '@testing-library/user-event';
5
+ import {
6
+ RichieContextFactory as mockRichieContextFactory,
7
+ UserFactory,
8
+ } from 'utils/test/factories/richie';
9
+ import { render } from 'utils/test/render';
10
+ import { BaseJoanieAppWrapper } from 'utils/test/wrappers/BaseJoanieAppWrapper';
11
+ import OpenEdxFullNameForm from 'components/OpenEdxFullNameForm/index';
12
+ import { SaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
13
+ import { SaleTunnelContextFactory } from 'utils/test/factories/joanie';
14
+ import { OpenEdxApiProfileFactory } from 'utils/test/factories/openEdx';
15
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
16
+ import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
17
+ import { AppWrapperProps } from 'utils/test/wrappers/types';
18
+ import { expectBannerError } from 'utils/test/expectBanner';
19
+
20
+ jest.mock('utils/context', () => ({
21
+ __esModule: true,
22
+ default: mockRichieContextFactory({
23
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
24
+ joanie_backend: { endpoint: 'https://joanie.endpoint' },
25
+ }).one(),
26
+ }));
27
+
28
+ jest.mock('utils/errors/handle', () => ({
29
+ handle: jest.fn(),
30
+ }));
31
+
32
+ describe('OpenEdxFullNameForm', () => {
33
+ let submitCallbacks: Record<PropertyKey, () => Promise<void>> = {};
34
+ const Wrapper = ({ children, ...props }: PropsWithChildren<AppWrapperProps>) => (
35
+ <BaseJoanieAppWrapper {...props}>
36
+ <SaleTunnelContext.Provider
37
+ value={SaleTunnelContextFactory({
38
+ registerSubmitCallback: (key: string, callback: () => Promise<void>) => {
39
+ submitCallbacks[key] = callback;
40
+ },
41
+ }).one()}
42
+ >
43
+ {children}
44
+ </SaleTunnelContext.Provider>
45
+ ,
46
+ </BaseJoanieAppWrapper>
47
+ );
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ fetchMock.restore();
52
+ submitCallbacks = {};
53
+ });
54
+ setupJoanieSession();
55
+
56
+ it('should not populate form with username', async () => {
57
+ const user = UserFactory({ full_name: '' }).one();
58
+ const queryClient = createTestQueryClient({ user });
59
+ const { 'pref-lang': prefLang, ...profile } = OpenEdxApiProfileFactory({
60
+ name: user.full_name,
61
+ username: user.username,
62
+ email: user.email,
63
+ }).one();
64
+ fetchMock.get(`https://auth.test/api/user/v1/accounts/${user.username}`, profile);
65
+ fetchMock.get(`https://auth.test/api/user/v1/preferences/${user.username}`, {
66
+ 'pref-lang': prefLang,
67
+ });
68
+
69
+ render(<OpenEdxFullNameForm />, {
70
+ queryOptions: { client: queryClient },
71
+ wrapper: Wrapper,
72
+ });
73
+
74
+ const $input = await screen.findByRole('textbox', { name: 'Full name' });
75
+ expect($input).toHaveValue('');
76
+ });
77
+
78
+ it('should populate form with existing full name', async () => {
79
+ const user = UserFactory().one();
80
+ const queryClient = createTestQueryClient({ user });
81
+ const { 'pref-lang': prefLang, ...profile } = OpenEdxApiProfileFactory({
82
+ name: user.full_name,
83
+ username: user.username,
84
+ email: user.email,
85
+ }).one();
86
+ fetchMock.get(`https://auth.test/api/user/v1/accounts/${user.username}`, profile);
87
+ fetchMock.get(`https://auth.test/api/user/v1/preferences/${user.username}`, {
88
+ 'pref-lang': prefLang,
89
+ });
90
+
91
+ render(<OpenEdxFullNameForm />, {
92
+ queryOptions: { client: queryClient },
93
+ wrapper: Wrapper,
94
+ });
95
+
96
+ const $input = await screen.findByRole('textbox', { name: 'Full name' });
97
+ expect($input).toHaveValue(user.full_name);
98
+ });
99
+
100
+ it('should require a value to submit the form', async () => {
101
+ const user = UserFactory({ full_name: '' }).one();
102
+ const queryClient = createTestQueryClient({ user });
103
+ const { 'pref-lang': prefLang, ...profile } = OpenEdxApiProfileFactory({
104
+ name: user.full_name,
105
+ username: user.username,
106
+ email: user.email,
107
+ }).one();
108
+ fetchMock.get(`https://auth.test/api/user/v1/accounts/${user.username}`, profile);
109
+ fetchMock.get(`https://auth.test/api/user/v1/preferences/${user.username}`, {
110
+ 'pref-lang': prefLang,
111
+ });
112
+
113
+ expect(submitCallbacks).toEqual({});
114
+
115
+ render(<OpenEdxFullNameForm />, {
116
+ queryOptions: { client: queryClient },
117
+ wrapper: Wrapper,
118
+ });
119
+
120
+ expect(submitCallbacks.hasOwnProperty('openEdxFullNameForm')).toBe(true);
121
+
122
+ const $input = await screen.findByRole('textbox', { name: 'Full name' });
123
+ expect($input).toHaveValue('');
124
+
125
+ // Submit the form
126
+ await act(async () => {
127
+ await expect(submitCallbacks.openEdxFullNameForm()).rejects.not.toBeUndefined();
128
+ });
129
+
130
+ screen.getByText('This field is required.');
131
+ });
132
+
133
+ it('should require a value with at least 3 chars to submit the form', async () => {
134
+ const user = UserFactory({ full_name: '' }).one();
135
+ const queryClient = createTestQueryClient({ user });
136
+ const { 'pref-lang': prefLang, ...profile } = OpenEdxApiProfileFactory({
137
+ name: user.full_name,
138
+ username: user.username,
139
+ email: user.email,
140
+ }).one();
141
+ fetchMock.get(`https://auth.test/api/user/v1/accounts/${user.username}`, profile);
142
+ fetchMock.get(`https://auth.test/api/user/v1/preferences/${user.username}`, {
143
+ 'pref-lang': prefLang,
144
+ });
145
+
146
+ expect(submitCallbacks).toEqual({});
147
+
148
+ render(<OpenEdxFullNameForm />, {
149
+ queryOptions: { client: queryClient },
150
+ wrapper: Wrapper,
151
+ });
152
+
153
+ expect(submitCallbacks.hasOwnProperty('openEdxFullNameForm')).toBe(true);
154
+
155
+ const $input = await screen.findByRole('textbox', { name: 'Full name' });
156
+ expect($input).toHaveValue('');
157
+
158
+ const eventHandler = userEvent.setup();
159
+ await eventHandler.type($input, 'Jo');
160
+
161
+ // Submit the form
162
+ await act(async () => {
163
+ await expect(submitCallbacks.openEdxFullNameForm()).rejects.not.toBeUndefined();
164
+ });
165
+
166
+ screen.getByText('The minimum length is 3 chars.');
167
+ });
168
+
169
+ it('should submit the form', async () => {
170
+ const user = UserFactory({ full_name: '' }).one();
171
+ const queryClient = createTestQueryClient({ user });
172
+ const { 'pref-lang': prefLang, ...profile } = OpenEdxApiProfileFactory({
173
+ name: user.full_name,
174
+ username: user.username,
175
+ email: user.email,
176
+ }).one();
177
+ fetchMock
178
+ .get(`https://auth.test/api/user/v1/accounts/${user.username}`, profile)
179
+ .get(`https://auth.test/api/user/v1/preferences/${user.username}`, {
180
+ 'pref-lang': prefLang,
181
+ });
182
+
183
+ expect(submitCallbacks).toEqual({});
184
+
185
+ render(<OpenEdxFullNameForm />, {
186
+ queryOptions: { client: queryClient },
187
+ wrapper: Wrapper,
188
+ });
189
+
190
+ expect(submitCallbacks.hasOwnProperty('openEdxFullNameForm')).toBe(true);
191
+
192
+ const $input = await screen.findByRole('textbox', { name: 'Full name' });
193
+ expect($input).toHaveValue('');
194
+
195
+ const eventHandler = userEvent.setup();
196
+ await eventHandler.type($input, 'John Doe');
197
+
198
+ // Submit the form
199
+ fetchMock
200
+ .patch(`https://auth.test/api/user/v1/accounts/${user.username}`, 200)
201
+ .patch(`https://auth.test/api/user/v1/preferences/${user.username}`, 200)
202
+ .get('https://auth.test/api/v1.0/user/me', { ...user, full_name: 'John Doe' })
203
+ .get(
204
+ `https://auth.test/api/user/v1/accounts/JohnDoe`,
205
+ { ...profile, name: 'John Doe' },
206
+ { overwriteRoutes: true },
207
+ );
208
+
209
+ await act(async () => {
210
+ await submitCallbacks.openEdxFullNameForm();
211
+ });
212
+
213
+ expect($input).toHaveValue('John Doe');
214
+ });
215
+
216
+ it('should display error if request to retrieve account fails', async () => {
217
+ const user = UserFactory().one();
218
+ const queryClient = createTestQueryClient({ user });
219
+ fetchMock.get(`https://auth.test/api/user/v1/accounts/${user.username}`, 500);
220
+ fetchMock.get(`https://auth.test/api/user/v1/preferences/${user.username}`, 500);
221
+
222
+ render(<OpenEdxFullNameForm />, {
223
+ queryOptions: { client: queryClient },
224
+ wrapper: Wrapper,
225
+ });
226
+
227
+ await expectBannerError('An error occurred while fetching your profile. Please retry later.');
228
+ });
229
+ });
@@ -9,7 +9,6 @@ import useOpenEdxProfile from 'hooks/useOpenEdxProfile';
9
9
  import Form, { getLocalizedCunninghamErrorProp } from 'components/Form';
10
10
  import { Spinner } from 'components/Spinner';
11
11
  import Banner, { BannerType } from 'components/Banner';
12
- import { UserHelper } from 'utils/UserHelper';
13
12
  import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
14
13
 
15
14
  const messages = defineMessages({
@@ -51,7 +50,7 @@ export interface OpenEdxFullNameFormValues {
51
50
  }
52
51
 
53
52
  const validationSchema = Yup.object().shape({
54
- name: Yup.string().required(),
53
+ name: Yup.string().required().min(3),
55
54
  });
56
55
 
57
56
  const OpenEdxFullNameForm = () => {
@@ -70,7 +69,7 @@ const OpenEdxFullNameForm = () => {
70
69
 
71
70
  const defaultValues = useMemo(
72
71
  () => ({
73
- name: (openEdxProfileData ? UserHelper.getName(openEdxProfileData) : '')?.trim(),
72
+ name: (openEdxProfileData?.name || '')?.trim(),
74
73
  }),
75
74
  [openEdxProfileData],
76
75
  );
@@ -82,19 +81,20 @@ const OpenEdxFullNameForm = () => {
82
81
  resolver: yupResolver(validationSchema),
83
82
  });
84
83
 
85
- const { register, handleSubmit, reset, formState } = form;
84
+ const { getValues, register, handleSubmit, reset, formState } = form;
86
85
 
87
86
  useEffect(() => {
88
87
  if (openEdxProfileData) {
89
- reset({ name: (openEdxProfileData ? UserHelper.getName(openEdxProfileData) : '')?.trim() });
88
+ reset({ name: (openEdxProfileData?.name || '')?.trim() });
90
89
  }
91
90
  }, [openEdxProfileData]);
92
91
 
93
92
  useEffect(() => {
94
93
  registerSubmitCallback('openEdxFullNameForm', async () => {
95
94
  return new Promise<void>((resolve, reject) => {
95
+ const { name } = getValues();
96
96
  // Don't save if the form has not been modified.
97
- if (!formState.isDirty) {
97
+ if (name && !formState.isDirty) {
98
98
  resolve();
99
99
  return;
100
100
  }
@@ -110,7 +110,7 @@ const OpenEdxFullNameForm = () => {
110
110
  },
111
111
  onError: (e) => reject(e),
112
112
  });
113
- })();
113
+ }, reject)();
114
114
  });
115
115
  });
116
116
  return () => {
@@ -29,7 +29,7 @@ const LyraPopIn = ({
29
29
  if (error && typeof error === 'string') handle(`[LyraPopIn] - ${error}`);
30
30
 
31
31
  if (shouldAbort.current) {
32
- onError(PaymentErrorMessageId.ERROR_ABORTING);
32
+ onError(PaymentErrorMessageId.ERROR_ABORT);
33
33
  } else if (typeof error === 'string') {
34
34
  onError(error);
35
35
  } else {
@@ -109,7 +109,7 @@ const LyraPopIn = ({
109
109
 
110
110
  const handleClosePopIn = () => {
111
111
  if (shouldAbort.current === true) {
112
- onError(PaymentErrorMessageId.ERROR_ABORTING);
112
+ onError(PaymentErrorMessageId.ERROR_ABORT);
113
113
  }
114
114
  };
115
115
 
@@ -27,7 +27,7 @@ const PayplugLightbox = ({
27
27
  switch (event.data) {
28
28
  case 'closePayPlugFrame':
29
29
  ref.current = setTimeout(() => {
30
- onError(PaymentErrorMessageId.ERROR_ABORTING);
30
+ onError(PaymentErrorMessageId.ERROR_ABORT);
31
31
  }, 2000);
32
32
  break;
33
33
  }
@@ -9,10 +9,7 @@ const PaymentInterface = ({ onError, onSuccess }: PaymentInterfaceProps) => (
9
9
  >
10
10
  Simulate payment failure
11
11
  </button>
12
- <button
13
- data-testid="payment-abort"
14
- onClick={() => onError(PaymentErrorMessageId.ERROR_ABORTING)}
15
- >
12
+ <button data-testid="payment-abort" onClick={() => onError(PaymentErrorMessageId.ERROR_ABORT)}>
16
13
  Simulate payment abort
17
14
  </button>
18
15
  <button data-testid="payment-success" onClick={onSuccess}>
@@ -1,10 +1,13 @@
1
1
  export enum PaymentErrorMessageId {
2
2
  ERROR_ABORT = 'errorAbort',
3
- ERROR_ABORTING = 'errorAborting',
3
+ ERROR_DEFAULT = 'errorDefault',
4
+ }
5
+
6
+ export enum SubscriptionErrorMessageId {
7
+ ERROR_ABORT = 'errorAbort',
4
8
  ERROR_ADDRESS = 'errorAddress',
5
9
  ERROR_DEFAULT = 'errorDefault',
6
10
  ERROR_FULL_PRODUCT = 'errorFullProduct',
7
- ERROR_TERMS = 'errorTerms',
8
11
  }
9
12
 
10
13
  export enum PaymentProviders {
@@ -4,6 +4,7 @@ import { IntlProvider } from 'react-intl';
4
4
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import { CunninghamProvider } from '@openfun/cunningham-react';
7
+ import queryString from 'query-string';
7
8
  import {
8
9
  CourseStateFactory,
9
10
  PacedCourseFactory,
@@ -17,7 +18,7 @@ import {
17
18
  } from 'utils/test/factories/joanie';
18
19
  import { SessionProvider } from 'contexts/SessionContext';
19
20
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
20
- import { ProductType } from 'types/Joanie';
21
+ import { NOT_CANCELED_ORDER_STATES, ProductType } from 'types/Joanie';
21
22
  import { Priority } from 'types';
22
23
  import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
23
24
  import { User } from 'types/User';
@@ -102,11 +103,14 @@ describe('PurchaseButton', () => {
102
103
  it('shows cta to open sale tunnel when user is authenticated', async () => {
103
104
  const courseCode = '00000';
104
105
  const product = ProductFactory().one();
106
+ const orderQueryParameters = {
107
+ course_code: courseCode,
108
+ product_id: product.id,
109
+ state: NOT_CANCELED_ORDER_STATES,
110
+ };
111
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
105
112
  fetchMock
106
- .get(
107
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
108
- {},
109
- )
113
+ .get(url, {})
110
114
  .get(
111
115
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
112
116
  [],
@@ -142,11 +146,14 @@ describe('PurchaseButton', () => {
142
146
  const product = ProductFactory({ remaining_order_count: null }).one();
143
147
  fetchMock.get(`https://demo.endpoint/api/user/v1/accounts/${user.username}`, {});
144
148
  fetchMock.get(`https://demo.endpoint/api/user/v1/preferences/${user.username}`, {});
149
+ const orderQueryParameters = {
150
+ course_code: courseCode,
151
+ product_id: product.id,
152
+ state: NOT_CANCELED_ORDER_STATES,
153
+ };
154
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
145
155
  fetchMock
146
- .get(
147
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
148
- {},
149
- )
156
+ .get(url, {})
150
157
  .get(
151
158
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
152
159
  [],
@@ -180,11 +187,14 @@ describe('PurchaseButton', () => {
180
187
  it('shows cta to open sale tunnel when remaining orders is undefined', async () => {
181
188
  const courseCode = '00000';
182
189
  const product = ProductFactory().one();
190
+ const orderQueryParameters = {
191
+ course_code: courseCode,
192
+ product_id: product.id,
193
+ state: NOT_CANCELED_ORDER_STATES,
194
+ };
195
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
183
196
  fetchMock
184
- .get(
185
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
186
- {},
187
- )
197
+ .get(url, {})
188
198
  .get(
189
199
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
190
200
  [],
@@ -220,10 +230,13 @@ describe('PurchaseButton', () => {
220
230
  it('renders a disabled CTA if the product have no remaining orders', async () => {
221
231
  const courseCode = '00000';
222
232
  const product = ProductFactory({ remaining_order_count: 0 }).one();
223
- fetchMock.get(
224
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
225
- {},
226
- );
233
+ const orderQueryParameters = {
234
+ course_code: courseCode,
235
+ product_id: product.id,
236
+ state: NOT_CANCELED_ORDER_STATES,
237
+ };
238
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
239
+ fetchMock.get(url, {});
227
240
  render(
228
241
  <Wrapper client={createTestQueryClient({ user: richieUser })}>
229
242
  <PurchaseButton
@@ -256,10 +269,14 @@ describe('PurchaseButton', () => {
256
269
  const courseCode = '00000';
257
270
  const product = ProductFactory({ ...productData, type: ProductType.CREDENTIAL }).one();
258
271
  product.target_courses[0].course_runs = [];
259
- fetchMock.get(
260
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
261
- {},
262
- );
272
+
273
+ const orderQueryParameters = {
274
+ course_code: courseCode,
275
+ product_id: product.id,
276
+ state: NOT_CANCELED_ORDER_STATES,
277
+ };
278
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
279
+ fetchMock.get(url, {});
263
280
 
264
281
  render(
265
282
  <Wrapper client={createTestQueryClient({ user: richieUser })}>
@@ -305,10 +322,14 @@ describe('PurchaseButton', () => {
305
322
  const product = CertificateProductFactory().one();
306
323
  const enrollment = EnrollmentFactory().one();
307
324
  enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
308
- fetchMock.get(
309
- `https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollment.id}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
310
- {},
311
- );
325
+
326
+ const orderQueryParameters = {
327
+ enrollment_id: enrollment.id,
328
+ product_id: product.id,
329
+ state: NOT_CANCELED_ORDER_STATES,
330
+ };
331
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
332
+ fetchMock.get(url, {});
312
333
 
313
334
  render(
314
335
  <Wrapper client={createTestQueryClient({ user: true })}>
@@ -359,10 +380,13 @@ describe('PurchaseButton', () => {
359
380
  const enrollment = EnrollmentFactory().one();
360
381
  enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
361
382
 
362
- fetchMock.get(
363
- `https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollment.id}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
364
- {},
365
- );
383
+ const orderQueryParameters = {
384
+ enrollment_id: enrollment.id,
385
+ product_id: product.id,
386
+ state: NOT_CANCELED_ORDER_STATES,
387
+ };
388
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
389
+ fetchMock.get(url, {});
366
390
 
367
391
  render(
368
392
  <Wrapper client={createTestQueryClient({ user: true })}>
@@ -388,10 +412,14 @@ describe('PurchaseButton', () => {
388
412
  it('renders a disabled CTA if product has no target courses', async () => {
389
413
  const courseCode = '00000';
390
414
  const product = ProductFactory().one();
391
- fetchMock.get(
392
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
393
- {},
394
- );
415
+
416
+ const orderQueryParameters = {
417
+ course_code: courseCode,
418
+ product_id: product.id,
419
+ state: NOT_CANCELED_ORDER_STATES,
420
+ };
421
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
422
+ fetchMock.get(url, {});
395
423
  product.target_courses = [];
396
424
 
397
425
  render(
@@ -419,10 +447,14 @@ describe('PurchaseButton', () => {
419
447
  it('does not render CTA if disabled property is false', async () => {
420
448
  const courseCode = '00000';
421
449
  const product = ProductFactory().one();
422
- fetchMock.get(
423
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
424
- {},
425
- );
450
+
451
+ const orderQueryParameters = {
452
+ course_code: courseCode,
453
+ product_id: product.id,
454
+ state: NOT_CANCELED_ORDER_STATES,
455
+ };
456
+ const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
457
+ fetchMock.get(url, {});
426
458
 
427
459
  render(
428
460
  <Wrapper client={createTestQueryClient({ user: true })}>
@@ -59,10 +59,11 @@ describe('AddressSelector', () => {
59
59
  setBillingAddress,
60
60
  setCreditCard: jest.fn(),
61
61
  onPaymentSuccess: jest.fn(),
62
- step: SaleTunnelStep.PAYMENT,
62
+ step: SaleTunnelStep.IDLE,
63
63
  registerSubmitCallback: jest.fn(),
64
64
  unregisterSubmitCallback: jest.fn(),
65
65
  runSubmitCallbacks: jest.fn(),
66
+ nextStep: jest.fn(),
66
67
  }),
67
68
  [billingAddress],
68
69
  );
@@ -1,8 +1,8 @@
1
1
  import { SaleTunnelProps } from 'components/SaleTunnel/index';
2
2
  import { CertificateProduct } from 'types/Joanie';
3
3
  import { GenericSaleTunnel } from 'components/SaleTunnel/GenericSaleTunnel';
4
- import { GenericPaymentButton } from 'components/SaleTunnel/GenericPaymentButton';
5
4
  import { CertificateProductPath } from 'components/SaleTunnel/CertificateSaleTunnel/CertificateProductPath';
5
+ import SubscriptionButton from 'components/SaleTunnel/SubscriptionButton';
6
6
 
7
7
  interface CertificateSaleTunnelProps extends Omit<SaleTunnelProps, 'product'> {
8
8
  product: CertificateProduct;
@@ -23,7 +23,7 @@ const CertificatePaymentButton = ({
23
23
  enrollment,
24
24
  }: Pick<CertificateSaleTunnelProps, 'enrollment'>) => {
25
25
  return (
26
- <GenericPaymentButton
26
+ <SubscriptionButton
27
27
  buildOrderPayload={(payload) => {
28
28
  return {
29
29
  ...payload,
@@ -1,8 +1,8 @@
1
1
  import { CredentialProduct } from 'types/Joanie';
2
2
  import { GenericSaleTunnel } from 'components/SaleTunnel/GenericSaleTunnel';
3
3
  import { SaleTunnelProps } from 'components/SaleTunnel/index';
4
- import { GenericPaymentButton } from 'components/SaleTunnel/GenericPaymentButton';
5
4
  import { CredentialProductPath } from 'components/SaleTunnel/CredentialSaleTunnel/CredentialProductPath';
5
+ import SubscriptionButton from 'components/SaleTunnel/SubscriptionButton';
6
6
 
7
7
  interface CredentialSaleTunnelProps extends Omit<SaleTunnelProps, 'product'> {
8
8
  product: CredentialProduct;
@@ -21,17 +21,13 @@ export const CredentialSaleTunnel = (props: CredentialSaleTunnelProps) => {
21
21
 
22
22
  const CredentialPaymentButton = ({
23
23
  course,
24
- orderGroup,
25
24
  }: Pick<CredentialSaleTunnelProps, 'course' | 'orderGroup'>) => {
26
25
  return (
27
- <GenericPaymentButton
28
- buildOrderPayload={(payload) => {
29
- return {
30
- ...payload,
31
- course_code: course!.code,
32
- ...(orderGroup ? { order_group_id: orderGroup.id } : {}),
33
- };
34
- }}
26
+ <SubscriptionButton
27
+ buildOrderPayload={(payload) => ({
28
+ ...payload,
29
+ course_code: course!.code,
30
+ })}
35
31
  />
36
32
  );
37
33
  };