richie-education 2.29.1-dev32 → 2.29.1-dev37
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/components/ContractStatus/index.spec.tsx +1 -1
- package/js/components/ContractStatus/index.tsx +1 -1
- package/js/components/PurchaseButton/index.tsx +9 -27
- package/js/components/SaleTunnel/index.tsx +2 -1
- package/js/pages/DashboardOrderLayout/index.spec.tsx +10 -1
- package/js/utils/ProductHelper/index.spec.ts +322 -166
- package/js/utils/ProductHelper/index.ts +32 -0
- package/js/widgets/Dashboard/components/DashboardItem/Contract/index.spec.tsx +2 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/CertificateItem/index.tsx +51 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/ContractItem/index.tsx +52 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemNotResumable.spec.tsx +109 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +105 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +103 -324
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +59 -8
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +12 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemSavePaymentMethod.spec.tsx +116 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +174 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +2 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +150 -0
- package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +29 -3
- package/package.json +2 -2
|
@@ -23,7 +23,7 @@ describe('<ContractStatus />', () => {
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
expect(
|
|
26
|
-
screen.queryByText('You have to sign this training contract to
|
|
26
|
+
screen.queryByText('You have to sign this training contract to finalize your subscription.'),
|
|
27
27
|
).toBeInTheDocument();
|
|
28
28
|
|
|
29
29
|
expect(screen.queryByText(/You signed this training contract/)).not.toBeInTheDocument();
|
|
@@ -15,7 +15,7 @@ const messages = defineMessages({
|
|
|
15
15
|
id: 'components.ContractStatus.organizationSignedOn',
|
|
16
16
|
},
|
|
17
17
|
waitingLearnerSignature: {
|
|
18
|
-
defaultMessage: 'You have to sign this training contract to
|
|
18
|
+
defaultMessage: 'You have to sign this training contract to finalize your subscription.',
|
|
19
19
|
description: 'Label displayed when a training contract need to be signed by the learner',
|
|
20
20
|
id: 'components.ContractStatus.waitingSignature',
|
|
21
21
|
},
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import c from 'classnames';
|
|
2
2
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
3
|
import { Button, ButtonProps, useModal } from '@openfun/cunningham-react';
|
|
5
4
|
import { useSession } from 'contexts/SessionContext';
|
|
6
5
|
import * as Joanie from 'types/Joanie';
|
|
7
|
-
import { isOpenedCourseRunCertificate, isOpenedCourseRunCredential } from 'utils/CourseRuns';
|
|
8
6
|
import { SaleTunnel, SaleTunnelProps } from 'components/SaleTunnel';
|
|
9
|
-
import { Organization } from 'types/Joanie';
|
|
7
|
+
import { CourseLight, Organization } from 'types/Joanie';
|
|
10
8
|
import { PacedCourse } from 'types';
|
|
9
|
+
import { ProductHelper } from 'utils/ProductHelper';
|
|
11
10
|
|
|
12
11
|
const messages = defineMessages({
|
|
13
12
|
loginToPurchase: {
|
|
@@ -53,7 +52,7 @@ interface PurchaseButtonPropsBase {
|
|
|
53
52
|
|
|
54
53
|
interface CredentialPurchaseButtonProps extends PurchaseButtonPropsBase {
|
|
55
54
|
product: Joanie.CredentialProduct;
|
|
56
|
-
course: PacedCourse;
|
|
55
|
+
course: PacedCourse | CourseLight;
|
|
57
56
|
enrollment?: undefined;
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -77,26 +76,9 @@ const PurchaseButton = ({
|
|
|
77
76
|
const intl = useIntl();
|
|
78
77
|
const { user, login } = useSession();
|
|
79
78
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
throw new Error(
|
|
84
|
-
'Unable to instanciate PurchaseButton with a product CERTIFICATE without the according CourseRun.',
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
return isOpenedCourseRunCertificate(enrollment.course_run.state);
|
|
88
|
-
}
|
|
89
|
-
return (
|
|
90
|
-
product.target_courses.length > 0 &&
|
|
91
|
-
product.target_courses.every(({ course_runs }) =>
|
|
92
|
-
course_runs.some((targetCourseRun) => isOpenedCourseRunCredential(targetCourseRun.state)),
|
|
93
|
-
)
|
|
94
|
-
);
|
|
95
|
-
}, [product]);
|
|
96
|
-
|
|
97
|
-
const hasAtLeastOneRemainingOrder =
|
|
98
|
-
typeof product?.remaining_order_count !== 'number' || product.remaining_order_count > 0;
|
|
99
|
-
const isPurchasable = hasAtLeastOneRemainingOrder && hasAtLeastOneCourseRun;
|
|
79
|
+
const hasOpenedTargetCourse = ProductHelper.hasOpenedTargetCourse(product, enrollment);
|
|
80
|
+
const hasRemainingSeat = ProductHelper.hasRemainingSeats(product);
|
|
81
|
+
const isPurchasable = hasRemainingSeat && hasOpenedTargetCourse;
|
|
100
82
|
|
|
101
83
|
const saleTunnelModal = useModal({
|
|
102
84
|
isOpenDefault: false,
|
|
@@ -121,7 +103,7 @@ const PurchaseButton = ({
|
|
|
121
103
|
data-testid="PurchaseButton__cta"
|
|
122
104
|
className={c('purchase-button__cta', className)}
|
|
123
105
|
onClick={() => {
|
|
124
|
-
if (
|
|
106
|
+
if (hasOpenedTargetCourse) {
|
|
125
107
|
saleTunnelModal.open();
|
|
126
108
|
}
|
|
127
109
|
}}
|
|
@@ -136,7 +118,7 @@ const PurchaseButton = ({
|
|
|
136
118
|
>
|
|
137
119
|
{product.call_to_action}
|
|
138
120
|
</Button>
|
|
139
|
-
{!
|
|
121
|
+
{!hasOpenedTargetCourse && (
|
|
140
122
|
<p className="purchase-button__no-course-run">
|
|
141
123
|
<FormattedMessage
|
|
142
124
|
{...(product.type === Joanie.ProductType.CREDENTIAL
|
|
@@ -145,7 +127,7 @@ const PurchaseButton = ({
|
|
|
145
127
|
/>
|
|
146
128
|
</p>
|
|
147
129
|
)}
|
|
148
|
-
{
|
|
130
|
+
{hasOpenedTargetCourse && !hasRemainingSeat && (
|
|
149
131
|
<p className="purchase-button__no-course-run">
|
|
150
132
|
<FormattedMessage {...messages.noRemainingOrder} />
|
|
151
133
|
</p>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ModalProps } from '@openfun/cunningham-react';
|
|
2
2
|
import {
|
|
3
3
|
CertificateProduct,
|
|
4
|
+
CourseLight,
|
|
4
5
|
CredentialProduct,
|
|
5
6
|
Enrollment,
|
|
6
7
|
Order,
|
|
@@ -17,7 +18,7 @@ export interface SaleTunnelProps extends Pick<ModalProps, 'isOpen' | 'onClose'>
|
|
|
17
18
|
product: Product;
|
|
18
19
|
organizations?: Organization[];
|
|
19
20
|
|
|
20
|
-
course?: PacedCourse;
|
|
21
|
+
course?: PacedCourse | CourseLight;
|
|
21
22
|
enrollment?: Enrollment;
|
|
22
23
|
orderGroup?: OrderGroup;
|
|
23
24
|
onFinish?: (order: Order) => void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { findByRole, render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import fetchMock from 'fetch-mock';
|
|
3
|
+
import queryString from 'query-string';
|
|
3
4
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
4
|
-
import { CredentialOrder } from 'types/Joanie';
|
|
5
|
+
import { CredentialOrder, NOT_CANCELED_ORDER_STATES } from 'types/Joanie';
|
|
5
6
|
import { CredentialOrderFactory, TargetCourseFactory } from 'utils/test/factories/joanie';
|
|
6
7
|
import { mockCourseProductWithOrder } from 'utils/test/mockCourseProductWithOrder';
|
|
7
8
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
@@ -54,6 +55,14 @@ describe('<DashboardOrderLayout />', () => {
|
|
|
54
55
|
{ results: [order], next: null, previous: null, count: null },
|
|
55
56
|
{ overwriteRoutes: true },
|
|
56
57
|
);
|
|
58
|
+
const orderQueryParameters = {
|
|
59
|
+
course_code: order.course.code,
|
|
60
|
+
product_id: order.product_id,
|
|
61
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
62
|
+
};
|
|
63
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
64
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
65
|
+
fetchMock.get(url, [order]);
|
|
57
66
|
|
|
58
67
|
render(WrapperWithDashboard(LearnerDashboardPaths.ORDER.replace(':orderId', order.id)));
|
|
59
68
|
|
|
@@ -1,183 +1,339 @@
|
|
|
1
1
|
import { createIntl } from 'react-intl';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CertificateProductFactory,
|
|
4
|
+
CourseRunFactory,
|
|
5
|
+
CredentialProductFactory,
|
|
6
|
+
EnrollmentFactory,
|
|
7
|
+
ProductFactory,
|
|
8
|
+
TargetCourseFactory,
|
|
9
|
+
} from 'utils/test/factories/joanie';
|
|
10
|
+
import { CourseStateFactory } from 'utils/test/factories/richie';
|
|
11
|
+
import { Priority } from 'types';
|
|
3
12
|
import { ProductHelper } from '.';
|
|
4
13
|
|
|
5
14
|
describe('ProductHelper', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
target_courses: [
|
|
9
|
-
TargetCourseFactory({
|
|
10
|
-
course_runs: [
|
|
11
|
-
CourseRunFactory({
|
|
12
|
-
start: new Date('2023-08-13').toISOString(),
|
|
13
|
-
end: new Date('2024-04-27').toISOString(),
|
|
14
|
-
}).one(),
|
|
15
|
-
CourseRunFactory({
|
|
16
|
-
start: new Date('2023-09-17').toISOString(),
|
|
17
|
-
end: new Date('2024-05-29').toISOString(),
|
|
18
|
-
}).one(),
|
|
19
|
-
],
|
|
20
|
-
}).one(),
|
|
21
|
-
TargetCourseFactory({
|
|
22
|
-
course_runs: [
|
|
23
|
-
CourseRunFactory({
|
|
24
|
-
start: new Date('2024-01-19').toISOString(),
|
|
25
|
-
end: new Date('2025-02-17').toISOString(),
|
|
26
|
-
}).one(),
|
|
27
|
-
],
|
|
28
|
-
}).one(),
|
|
29
|
-
],
|
|
30
|
-
}).one();
|
|
31
|
-
|
|
32
|
-
expect(ProductHelper.getDateRange(product)).toEqual([
|
|
33
|
-
new Date('2023-08-13'),
|
|
34
|
-
new Date('2025-02-17'),
|
|
35
|
-
]);
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.resetAllMocks();
|
|
36
17
|
});
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
19
|
+
describe('getDateRange', () => {
|
|
20
|
+
it('should return the product date range processed from course runs dates', () => {
|
|
21
|
+
const product = ProductFactory({
|
|
22
|
+
target_courses: [
|
|
23
|
+
TargetCourseFactory({
|
|
24
|
+
course_runs: [
|
|
25
|
+
CourseRunFactory({
|
|
26
|
+
start: new Date('2023-08-13').toISOString(),
|
|
27
|
+
end: new Date('2024-04-27').toISOString(),
|
|
28
|
+
}).one(),
|
|
29
|
+
CourseRunFactory({
|
|
30
|
+
start: new Date('2023-09-17').toISOString(),
|
|
31
|
+
end: new Date('2024-05-29').toISOString(),
|
|
32
|
+
}).one(),
|
|
33
|
+
],
|
|
34
|
+
}).one(),
|
|
35
|
+
TargetCourseFactory({
|
|
36
|
+
course_runs: [
|
|
37
|
+
CourseRunFactory({
|
|
38
|
+
start: new Date('2024-01-19').toISOString(),
|
|
39
|
+
end: new Date('2025-02-17').toISOString(),
|
|
40
|
+
}).one(),
|
|
41
|
+
],
|
|
42
|
+
}).one(),
|
|
43
|
+
],
|
|
44
|
+
}).one();
|
|
45
|
+
|
|
46
|
+
expect(ProductHelper.getDateRange(product)).toEqual([
|
|
47
|
+
new Date('2023-08-13'),
|
|
48
|
+
new Date('2025-02-17'),
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return undefined minDate when there is a course run with an undefined start date', () => {
|
|
53
|
+
const product = ProductFactory({
|
|
54
|
+
target_courses: [
|
|
55
|
+
TargetCourseFactory({
|
|
56
|
+
course_runs: [
|
|
57
|
+
CourseRunFactory({
|
|
58
|
+
start: undefined,
|
|
59
|
+
end: new Date('2024-04-27').toISOString(),
|
|
60
|
+
}).one(),
|
|
61
|
+
CourseRunFactory({
|
|
62
|
+
start: new Date('2023-09-17').toISOString(),
|
|
63
|
+
end: new Date('2024-05-29').toISOString(),
|
|
64
|
+
}).one(),
|
|
65
|
+
],
|
|
66
|
+
}).one(),
|
|
67
|
+
TargetCourseFactory({
|
|
68
|
+
course_runs: [
|
|
69
|
+
CourseRunFactory({
|
|
70
|
+
start: new Date('2024-01-19').toISOString(),
|
|
71
|
+
end: new Date('2025-02-17').toISOString(),
|
|
72
|
+
}).one(),
|
|
73
|
+
],
|
|
74
|
+
}).one(),
|
|
75
|
+
],
|
|
76
|
+
}).one();
|
|
77
|
+
|
|
78
|
+
expect(ProductHelper.getDateRange(product)).toEqual([undefined, new Date('2025-02-17')]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return undefined maxDate when there is a course run with an undefined end date', () => {
|
|
82
|
+
const product = ProductFactory({
|
|
83
|
+
target_courses: [
|
|
84
|
+
TargetCourseFactory({
|
|
85
|
+
course_runs: [
|
|
86
|
+
CourseRunFactory({
|
|
87
|
+
start: new Date('2023-08-13').toISOString(),
|
|
88
|
+
end: undefined,
|
|
89
|
+
}).one(),
|
|
90
|
+
CourseRunFactory({
|
|
91
|
+
start: new Date('2023-09-17').toISOString(),
|
|
92
|
+
end: new Date('2024-05-29').toISOString(),
|
|
93
|
+
}).one(),
|
|
94
|
+
],
|
|
95
|
+
}).one(),
|
|
96
|
+
TargetCourseFactory({
|
|
97
|
+
course_runs: [
|
|
98
|
+
CourseRunFactory({
|
|
99
|
+
start: new Date('2024-01-19').toISOString(),
|
|
100
|
+
end: new Date('2025-02-17').toISOString(),
|
|
101
|
+
}).one(),
|
|
102
|
+
],
|
|
103
|
+
}).one(),
|
|
104
|
+
],
|
|
105
|
+
}).one();
|
|
66
106
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
target_courses: [
|
|
70
|
-
TargetCourseFactory({
|
|
71
|
-
course_runs: [
|
|
72
|
-
CourseRunFactory({
|
|
73
|
-
start: new Date('2023-08-13').toISOString(),
|
|
74
|
-
end: undefined,
|
|
75
|
-
}).one(),
|
|
76
|
-
CourseRunFactory({
|
|
77
|
-
start: new Date('2023-09-17').toISOString(),
|
|
78
|
-
end: new Date('2024-05-29').toISOString(),
|
|
79
|
-
}).one(),
|
|
80
|
-
],
|
|
81
|
-
}).one(),
|
|
82
|
-
TargetCourseFactory({
|
|
83
|
-
course_runs: [
|
|
84
|
-
CourseRunFactory({
|
|
85
|
-
start: new Date('2024-01-19').toISOString(),
|
|
86
|
-
end: new Date('2025-02-17').toISOString(),
|
|
87
|
-
}).one(),
|
|
88
|
-
],
|
|
89
|
-
}).one(),
|
|
90
|
-
],
|
|
91
|
-
}).one();
|
|
92
|
-
|
|
93
|
-
expect(ProductHelper.getDateRange(product)).toEqual([new Date('2023-08-13'), undefined]);
|
|
107
|
+
expect(ProductHelper.getDateRange(product)).toEqual([new Date('2023-08-13'), undefined]);
|
|
108
|
+
});
|
|
94
109
|
});
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
describe('getLanguages', () => {
|
|
112
|
+
it('should return the product languages processed from course runs languages', () => {
|
|
113
|
+
const product = ProductFactory({
|
|
114
|
+
target_courses: [
|
|
115
|
+
TargetCourseFactory({
|
|
116
|
+
course_runs: [
|
|
117
|
+
CourseRunFactory({
|
|
118
|
+
languages: ['fr', 'en'],
|
|
119
|
+
}).one(),
|
|
120
|
+
CourseRunFactory({
|
|
121
|
+
languages: ['fr', 'de'],
|
|
122
|
+
}).one(),
|
|
123
|
+
],
|
|
124
|
+
}).one(),
|
|
125
|
+
TargetCourseFactory({
|
|
126
|
+
course_runs: [
|
|
127
|
+
CourseRunFactory({
|
|
128
|
+
languages: ['fr', 'es'],
|
|
129
|
+
}).one(),
|
|
130
|
+
],
|
|
131
|
+
}).one(),
|
|
132
|
+
],
|
|
133
|
+
}).one();
|
|
134
|
+
|
|
135
|
+
expect(ProductHelper.getLanguages(product)).toEqual(['fr', 'en', 'de', 'es']);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return an empty array when there is no language', () => {
|
|
139
|
+
const product = ProductFactory({
|
|
140
|
+
target_courses: [
|
|
141
|
+
TargetCourseFactory({
|
|
142
|
+
course_runs: [
|
|
143
|
+
CourseRunFactory({
|
|
144
|
+
languages: [],
|
|
145
|
+
}).one(),
|
|
146
|
+
],
|
|
147
|
+
}).one(),
|
|
148
|
+
],
|
|
149
|
+
}).one();
|
|
150
|
+
|
|
151
|
+
expect(ProductHelper.getLanguages(product)).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return sorted human readable languages according to the active language', () => {
|
|
155
|
+
const product = ProductFactory({
|
|
156
|
+
target_courses: [
|
|
157
|
+
TargetCourseFactory({
|
|
158
|
+
course_runs: [
|
|
159
|
+
CourseRunFactory({
|
|
160
|
+
languages: ['fr', 'en'],
|
|
161
|
+
}).one(),
|
|
162
|
+
CourseRunFactory({
|
|
163
|
+
languages: ['fr', 'de'],
|
|
164
|
+
}).one(),
|
|
165
|
+
],
|
|
166
|
+
}).one(),
|
|
167
|
+
TargetCourseFactory({
|
|
168
|
+
course_runs: [
|
|
169
|
+
CourseRunFactory({
|
|
170
|
+
languages: ['fr', 'es'],
|
|
171
|
+
}).one(),
|
|
172
|
+
],
|
|
173
|
+
}).one(),
|
|
174
|
+
],
|
|
175
|
+
}).one();
|
|
176
|
+
|
|
177
|
+
const intl = createIntl({ locale: 'en' });
|
|
178
|
+
expect(ProductHelper.getLanguages(product, true, intl)).toEqual(
|
|
179
|
+
'English, French, German and Spanish',
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should return an empty string when there is no language', () => {
|
|
184
|
+
const product = ProductFactory({
|
|
185
|
+
target_courses: [
|
|
186
|
+
TargetCourseFactory({
|
|
187
|
+
course_runs: [
|
|
188
|
+
CourseRunFactory({
|
|
189
|
+
languages: [],
|
|
190
|
+
}).one(),
|
|
191
|
+
],
|
|
192
|
+
}).one(),
|
|
193
|
+
],
|
|
194
|
+
}).one();
|
|
195
|
+
|
|
196
|
+
const intl = createIntl({ locale: 'en' });
|
|
197
|
+
expect(ProductHelper.getLanguages(product, true, intl)).toEqual('');
|
|
198
|
+
});
|
|
120
199
|
});
|
|
121
200
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
|
|
201
|
+
describe('hasRemainingSeats', () => {
|
|
202
|
+
it('should return false when the product is undefined', () => {
|
|
203
|
+
expect(ProductHelper.hasRemainingSeats(undefined)).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should return true when the product has no order group', () => {
|
|
207
|
+
const product = ProductFactory({
|
|
208
|
+
remaining_order_count: null,
|
|
209
|
+
}).one();
|
|
210
|
+
|
|
211
|
+
expect(ProductHelper.hasRemainingSeats(product)).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should return true when the product has remaining seats', () => {
|
|
215
|
+
const product = ProductFactory({
|
|
216
|
+
remaining_order_count: 10,
|
|
217
|
+
}).one();
|
|
218
|
+
|
|
219
|
+
expect(ProductHelper.hasRemainingSeats(product)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return false when the product does not have remaining seats', () => {
|
|
223
|
+
const product = ProductFactory({
|
|
224
|
+
remaining_order_count: 0,
|
|
225
|
+
}).one();
|
|
226
|
+
|
|
227
|
+
expect(ProductHelper.hasRemainingSeats(product)).toBe(false);
|
|
228
|
+
});
|
|
136
229
|
});
|
|
137
230
|
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
231
|
+
describe('hasOpenedTargetCourse', () => {
|
|
232
|
+
const openedCourseRunFactory = CourseRunFactory({
|
|
233
|
+
state: CourseStateFactory({
|
|
234
|
+
priority: Priority.ONGOING_OPEN,
|
|
235
|
+
}).one(),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const closedCourseRunFactory = CourseRunFactory({
|
|
239
|
+
state: CourseStateFactory({
|
|
240
|
+
priority: Priority.ARCHIVED_CLOSED,
|
|
241
|
+
}).one(),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should return false when the product is undefined', () => {
|
|
245
|
+
expect(ProductHelper.hasOpenedTargetCourse(undefined)).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should throw an error when the product is a certificate and the enrollment is undefined', () => {
|
|
249
|
+
const product = CertificateProductFactory().one();
|
|
250
|
+
|
|
251
|
+
expect(() => ProductHelper.hasOpenedTargetCourse(product, undefined)).toThrowError(
|
|
252
|
+
'Unable to check if the certificate product relies on an opened course run without enrollment.',
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return true when the product is a certificate and the related course run is opened', () => {
|
|
257
|
+
const product = CertificateProductFactory().one();
|
|
258
|
+
const courseRun = openedCourseRunFactory.one();
|
|
259
|
+
const enrollment = EnrollmentFactory({ course_run: courseRun }).one();
|
|
260
|
+
|
|
261
|
+
expect(ProductHelper.hasOpenedTargetCourse(product, enrollment)).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should return false when the product is a certificate and the related course run is not opened', () => {
|
|
265
|
+
const product = CertificateProductFactory().one();
|
|
266
|
+
const courseRun = closedCourseRunFactory.one();
|
|
267
|
+
const enrollment = EnrollmentFactory({ course_run: courseRun }).one();
|
|
268
|
+
|
|
269
|
+
expect(ProductHelper.hasOpenedTargetCourse(product, enrollment)).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return false when the product is a credential and has not target courses', () => {
|
|
273
|
+
const product = CredentialProductFactory({ target_courses: [] }).one();
|
|
274
|
+
|
|
275
|
+
expect(ProductHelper.hasOpenedTargetCourse(product)).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should return false when the product is a credential and at least one target course has no opened course run', () => {
|
|
279
|
+
const targetCourseOpen = TargetCourseFactory({
|
|
280
|
+
course_runs: [openedCourseRunFactory.one(), closedCourseRunFactory.one()],
|
|
281
|
+
}).one();
|
|
282
|
+
const targetCourseClosed = TargetCourseFactory({
|
|
283
|
+
course_runs: [closedCourseRunFactory.one(), closedCourseRunFactory.one()],
|
|
284
|
+
}).one();
|
|
285
|
+
const product = CredentialProductFactory({
|
|
286
|
+
target_courses: [targetCourseOpen, targetCourseClosed],
|
|
287
|
+
}).one();
|
|
288
|
+
|
|
289
|
+
expect(ProductHelper.hasOpenedTargetCourse(product)).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should return false when the product is a credential and target courses has one opened course run', () => {
|
|
293
|
+
const targetCourseOpen = TargetCourseFactory({
|
|
294
|
+
course_runs: [openedCourseRunFactory.one(), closedCourseRunFactory.one()],
|
|
295
|
+
}).many(1);
|
|
296
|
+
const product = CredentialProductFactory({ target_courses: targetCourseOpen }).one();
|
|
297
|
+
|
|
298
|
+
expect(ProductHelper.hasOpenedTargetCourse(product)).toBe(true);
|
|
299
|
+
});
|
|
165
300
|
});
|
|
166
301
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
302
|
+
describe('isPurchasable', () => {
|
|
303
|
+
it('should return false when the product is undefined', () => {
|
|
304
|
+
expect(ProductHelper.isPurchasable(undefined)).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should return false when the product does not have opened target courses', () => {
|
|
308
|
+
jest.spyOn(ProductHelper, 'hasOpenedTargetCourse').mockReturnValue(false);
|
|
309
|
+
jest.spyOn(ProductHelper, 'hasRemainingSeats').mockReturnValue(true);
|
|
310
|
+
const product = ProductFactory().one();
|
|
311
|
+
|
|
312
|
+
expect(ProductHelper.isPurchasable(product)).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should return false when the product does not remaining seats', () => {
|
|
316
|
+
jest.spyOn(ProductHelper, 'hasOpenedTargetCourse').mockReturnValue(true);
|
|
317
|
+
jest.spyOn(ProductHelper, 'hasRemainingSeats').mockReturnValue(false);
|
|
318
|
+
const product = ProductFactory().one();
|
|
319
|
+
|
|
320
|
+
expect(ProductHelper.isPurchasable(product)).toBe(false);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should return false when the product does not have opened target courses and remaining seats', () => {
|
|
324
|
+
jest.spyOn(ProductHelper, 'hasOpenedTargetCourse').mockReturnValue(false);
|
|
325
|
+
jest.spyOn(ProductHelper, 'hasRemainingSeats').mockReturnValue(false);
|
|
326
|
+
const product = ProductFactory().one();
|
|
327
|
+
|
|
328
|
+
expect(ProductHelper.isPurchasable(product)).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return true when the product has opened target courses and remaining seats', () => {
|
|
332
|
+
jest.spyOn(ProductHelper, 'hasOpenedTargetCourse').mockReturnValue(true);
|
|
333
|
+
jest.spyOn(ProductHelper, 'hasRemainingSeats').mockReturnValue(true);
|
|
334
|
+
const product = ProductFactory().one();
|
|
335
|
+
|
|
336
|
+
expect(ProductHelper.isPurchasable(product)).toBe(true);
|
|
337
|
+
});
|
|
182
338
|
});
|
|
183
339
|
});
|