richie-education 2.25.0-b2.dev69 → 2.25.0-b2.dev71
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/widgets/Dashboard/components/DashboardItem/CourseEnrolling/hooks/useCourseRunPeriodMessage.ts +76 -0
- package/js/widgets/Dashboard/components/DashboardItem/{DashboardItemCourseEnrolling.spec.tsx → CourseEnrolling/index.spec.tsx} +44 -13
- package/js/widgets/Dashboard/components/DashboardItem/{DashboardItemCourseEnrolling.stories.tsx → CourseEnrolling/index.stories.tsx} +2 -2
- package/js/widgets/Dashboard/components/DashboardItem/{DashboardItemCourseEnrolling.tsx → CourseEnrolling/index.tsx} +38 -57
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.spec.tsx +7 -19
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +76 -24
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
2
|
+
import useDateRelative from 'hooks/useDateRelative';
|
|
3
|
+
import { Priority } from 'types';
|
|
4
|
+
import { CourseRun } from 'types/Joanie';
|
|
5
|
+
import useDateFormat, { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
6
|
+
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
onGoingRunPeriod: {
|
|
9
|
+
id: 'components.useCourseRunPeriodMessage.onGoingRunPeriod',
|
|
10
|
+
description: 'Text to display when a course run is on going.',
|
|
11
|
+
defaultMessage: 'This session started on {startDate} and will end on {endDate}',
|
|
12
|
+
},
|
|
13
|
+
futureRunPeriod: {
|
|
14
|
+
id: 'components.useCourseRunPeriodMessage.futureRunPeriod',
|
|
15
|
+
description: 'Text to display the period of a future course run.',
|
|
16
|
+
defaultMessage: 'This session starts {relativeStartDate}, the {startDate}',
|
|
17
|
+
},
|
|
18
|
+
onGoingEnrolledRunPeriod: {
|
|
19
|
+
id: 'components.useCourseRunPeriodMessage.onGoingEnrolledRunPeriod',
|
|
20
|
+
description: 'Text to display when a course run is ongoing and the user is enrolled to.',
|
|
21
|
+
defaultMessage: "You are enrolled for this session. It's open from {startDate} to {endDate}",
|
|
22
|
+
},
|
|
23
|
+
futureEnrolledRunPeriod: {
|
|
24
|
+
id: 'components.useCourseRunPeriodMessage.futureEnrolledRunPeriod',
|
|
25
|
+
description: 'Text to display when a course run is not yet opened and the user is enrolled to.',
|
|
26
|
+
defaultMessage:
|
|
27
|
+
'You are enrolled for this session. It starts {relativeStartDate}, the {startDate}.',
|
|
28
|
+
},
|
|
29
|
+
archivedEnrolledRunPeriod: {
|
|
30
|
+
id: 'components.useCourseRunPeriodMessage.archivedEnrolledRunPeriod',
|
|
31
|
+
description: 'Text to display when a course run is archived and the user is enrolled to.',
|
|
32
|
+
defaultMessage: 'You are enrolled for this session.',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const useCourseRunPeriodMessage = (courseRun: CourseRun, enrolled: boolean = false) => {
|
|
37
|
+
const intl = useIntl();
|
|
38
|
+
const formatDate = useDateFormat();
|
|
39
|
+
|
|
40
|
+
const relativeStartDate = useDateRelative(new Date(courseRun.start));
|
|
41
|
+
const startDate = formatDate(courseRun.start, DEFAULT_DATE_FORMAT);
|
|
42
|
+
const endDate = formatDate(courseRun.end, DEFAULT_DATE_FORMAT);
|
|
43
|
+
const isArchived = [Priority.ARCHIVED_CLOSED, Priority.ARCHIVED_OPEN].includes(
|
|
44
|
+
courseRun.state.priority,
|
|
45
|
+
);
|
|
46
|
+
const isOnGoing = [Priority.ONGOING_OPEN, Priority.ONGOING_CLOSED].includes(
|
|
47
|
+
courseRun.state.priority,
|
|
48
|
+
);
|
|
49
|
+
if (enrolled) {
|
|
50
|
+
if (isArchived) {
|
|
51
|
+
return intl.formatMessage(messages.archivedEnrolledRunPeriod);
|
|
52
|
+
}
|
|
53
|
+
if (isOnGoing) {
|
|
54
|
+
return intl.formatMessage(messages.onGoingEnrolledRunPeriod, {
|
|
55
|
+
startDate,
|
|
56
|
+
endDate,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return intl.formatMessage(messages.futureEnrolledRunPeriod, {
|
|
60
|
+
relativeStartDate,
|
|
61
|
+
startDate,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (isOnGoing) {
|
|
65
|
+
return intl.formatMessage(messages.onGoingRunPeriod, {
|
|
66
|
+
startDate,
|
|
67
|
+
endDate,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return intl.formatMessage(messages.futureRunPeriod, {
|
|
71
|
+
relativeStartDate,
|
|
72
|
+
startDate,
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default useCourseRunPeriodMessage;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { IntlProvider } from 'react-intl';
|
|
2
|
+
import { IntlProvider, createIntl } from 'react-intl';
|
|
3
3
|
import { PropsWithChildren } from 'react';
|
|
4
4
|
import { CredentialOrderFactory, EnrollmentFactory } from 'utils/test/factories/joanie';
|
|
5
5
|
import { Priority } from 'types';
|
|
@@ -8,7 +8,8 @@ import { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
|
8
8
|
import { CourseRunFactoryFromPriority } from 'utils/test/factories/richie';
|
|
9
9
|
import { noop } from 'utils';
|
|
10
10
|
import { computeState } from 'utils/CourseRuns';
|
|
11
|
-
import {
|
|
11
|
+
import { formatRelativeDate } from 'utils/relativeDate';
|
|
12
|
+
import { DashboardItemCourseEnrollingRun, Enrolled } from '.';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Most of the component of this file are tested from DashboardItemEnrollment.spec.tsx and
|
|
@@ -23,59 +24,89 @@ describe('<Enrolled/>', () => {
|
|
|
23
24
|
{
|
|
24
25
|
buttonTestLabel: 'and access course button',
|
|
25
26
|
priority: Priority.ONGOING_OPEN,
|
|
27
|
+
priorityLabel: 'ONGOING_OPEN',
|
|
26
28
|
expectButton: true,
|
|
29
|
+
expectLabelTemplate:
|
|
30
|
+
"You are enrolled for this session. It's open from %fromDate% to %toDate%",
|
|
27
31
|
},
|
|
28
32
|
{
|
|
29
33
|
buttonTestLabel: 'and no access course button',
|
|
30
34
|
priority: Priority.FUTURE_OPEN,
|
|
35
|
+
priorityLabel: 'FUTURE_OPEN',
|
|
31
36
|
expectButton: false,
|
|
37
|
+
expectLabelTemplate:
|
|
38
|
+
'You are enrolled for this session. It starts %fromRelativeDate%, the %fromDate%.',
|
|
32
39
|
},
|
|
33
40
|
{
|
|
34
41
|
buttonTestLabel: 'and access course button',
|
|
35
42
|
priority: Priority.ARCHIVED_OPEN,
|
|
43
|
+
priorityLabel: 'ARCHIVED_OPEN',
|
|
36
44
|
expectButton: true,
|
|
45
|
+
expectLabelTemplate: `You are enrolled for this session.`,
|
|
37
46
|
},
|
|
38
47
|
{
|
|
39
48
|
buttonTestLabel: 'and no access course button',
|
|
40
49
|
priority: Priority.FUTURE_NOT_YET_OPEN,
|
|
50
|
+
priorityLabel: 'FUTURE_NOT_YET_OPEN',
|
|
41
51
|
expectButton: false,
|
|
52
|
+
expectLabelTemplate:
|
|
53
|
+
'You are enrolled for this session. It starts %fromRelativeDate%, the %fromDate%.',
|
|
42
54
|
},
|
|
43
55
|
{
|
|
44
56
|
buttonTestLabel: 'and no access course button',
|
|
45
57
|
priority: Priority.FUTURE_CLOSED,
|
|
58
|
+
priorityLabel: 'FUTURE_CLOSED',
|
|
46
59
|
expectButton: false,
|
|
60
|
+
expectLabelTemplate:
|
|
61
|
+
'You are enrolled for this session. It starts %fromRelativeDate%, the %fromDate%.',
|
|
47
62
|
},
|
|
48
63
|
{
|
|
49
64
|
buttonTestLabel: 'and access course button',
|
|
50
65
|
priority: Priority.ONGOING_CLOSED,
|
|
66
|
+
priorityLabel: 'ONGOING_CLOSED',
|
|
51
67
|
expectButton: true,
|
|
68
|
+
expectLabelTemplate: `You are enrolled for this session. It's open from %fromDate% to %toDate%`,
|
|
52
69
|
},
|
|
53
70
|
{
|
|
54
71
|
buttonTestLabel: 'and access course button',
|
|
55
72
|
priority: Priority.ARCHIVED_CLOSED,
|
|
73
|
+
priorityLabel: 'ARCHIVED_CLOSED',
|
|
56
74
|
expectButton: true,
|
|
75
|
+
expectLabelTemplate: `You are enrolled for this session.`,
|
|
57
76
|
},
|
|
58
77
|
{
|
|
59
78
|
buttonTestLabel: 'and no access course button',
|
|
60
79
|
priority: Priority.TO_BE_SCHEDULED,
|
|
80
|
+
priorityLabel: 'TO_BE_SCHEDULED',
|
|
61
81
|
expectButton: false,
|
|
82
|
+
expectLabelTemplate:
|
|
83
|
+
'You are enrolled for this session. It starts %fromRelativeDate%, the %fromDate%.',
|
|
62
84
|
},
|
|
63
85
|
])(
|
|
64
|
-
'handles enrollments with priority=$
|
|
65
|
-
async ({ priority, expectButton }) => {
|
|
86
|
+
'handles enrollments with priority=$priorityLabel $buttonTestLabel',
|
|
87
|
+
async ({ priority, expectButton, expectLabelTemplate }) => {
|
|
66
88
|
const enrollment: Enrollment = EnrollmentFactory().one();
|
|
67
89
|
enrollment.course_run.state.priority = priority;
|
|
68
90
|
render(<Enrolled enrollment={enrollment} />, { wrapper });
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
const intl = createIntl({ locale: 'en' });
|
|
92
|
+
|
|
93
|
+
const fromDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
94
|
+
new Date(enrollment.course_run.start),
|
|
95
|
+
);
|
|
96
|
+
const fromRelativeDate = formatRelativeDate(
|
|
97
|
+
new Date(enrollment.course_run.start),
|
|
98
|
+
new Date(),
|
|
99
|
+
intl.locale,
|
|
78
100
|
);
|
|
101
|
+
const toDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
102
|
+
new Date(enrollment.course_run.end),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const expectLabel = expectLabelTemplate
|
|
106
|
+
.replace('%fromRelativeDate%', fromRelativeDate)
|
|
107
|
+
.replace('%fromDate%', fromDate)
|
|
108
|
+
.replace('%toDate%', toDate);
|
|
109
|
+
expect(await screen.findByText(expectLabel)).toBeInTheDocument();
|
|
79
110
|
if (expectButton) {
|
|
80
111
|
const link = screen.getByRole('link', { name: 'Access to course' });
|
|
81
112
|
expect(link).toBeEnabled();
|
|
@@ -4,8 +4,8 @@ import { faker } from '@faker-js/faker';
|
|
|
4
4
|
import { TargetCourseFactory } from 'utils/test/factories/joanie';
|
|
5
5
|
import { StorybookHelper } from 'utils/StorybookHelper';
|
|
6
6
|
import { Priority } from 'types';
|
|
7
|
-
import { enrollment } from '
|
|
8
|
-
import { DashboardItemCourseEnrolling } from '
|
|
7
|
+
import { enrollment } from '../stories.mock';
|
|
8
|
+
import { DashboardItemCourseEnrolling } from '.';
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
component: DashboardItemCourseEnrolling,
|
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
} from 'types/Joanie';
|
|
14
14
|
import { Spinner } from 'components/Spinner';
|
|
15
15
|
import Banner, { BannerType } from 'components/Banner';
|
|
16
|
-
import useDateFormat, { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
17
16
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
17
|
+
import useDateFormat from 'hooks/useDateFormat';
|
|
18
18
|
import { orderNeedsSignature } from 'widgets/Dashboard/components/DashboardItem/utils/order';
|
|
19
|
-
import { RouterButton } from '
|
|
20
|
-
import { useEnroll } from '
|
|
19
|
+
import { RouterButton } from 'widgets/Dashboard/components/RouterButton';
|
|
20
|
+
import { useEnroll } from 'widgets/Dashboard/hooks/useEnroll';
|
|
21
|
+
import useCourseRunPeriodMessage from './hooks/useCourseRunPeriodMessage';
|
|
21
22
|
|
|
22
23
|
const messages = defineMessages({
|
|
23
24
|
notEnrolled: {
|
|
@@ -41,16 +42,6 @@ const messages = defineMessages({
|
|
|
41
42
|
description: 'Button to access course when the user is enrolled',
|
|
42
43
|
defaultMessage: 'Access to course',
|
|
43
44
|
},
|
|
44
|
-
runPeriod: {
|
|
45
|
-
id: 'components.DashboardItemEnrollment.runPeriod',
|
|
46
|
-
description: 'Text to display the period of a course run',
|
|
47
|
-
defaultMessage: 'From {startDate} to {endDate}',
|
|
48
|
-
},
|
|
49
|
-
enrolledRunPeriod: {
|
|
50
|
-
id: 'components.DashboardItemEnrollment.enrolledRunPeriod',
|
|
51
|
-
description: 'Text to display the period of a course run',
|
|
52
|
-
defaultMessage: 'You are enrolled for the session from {startDate} to {endDate}',
|
|
53
|
-
},
|
|
54
45
|
enrolled: {
|
|
55
46
|
id: 'components.DashboardItemEnrollment.enrolled',
|
|
56
47
|
description: 'Text shown when user is enrolled in a course run',
|
|
@@ -152,7 +143,7 @@ const DashboardItemCourseEnrollingRuns = ({
|
|
|
152
143
|
const { enroll, isLoading, error } = useEnroll(enrollments, order);
|
|
153
144
|
|
|
154
145
|
// Hide runs with finished enrollment.
|
|
155
|
-
const
|
|
146
|
+
const courseRunOpenForEnrollmentList = useMemo(() => {
|
|
156
147
|
const activeEnrollment = CoursesHelper.findActiveEnrollment(course, enrollments);
|
|
157
148
|
return course.course_runs
|
|
158
149
|
.map((courseRun) => ({
|
|
@@ -160,6 +151,9 @@ const DashboardItemCourseEnrollingRuns = ({
|
|
|
160
151
|
selected: activeEnrollment?.course_run.id === courseRun.id,
|
|
161
152
|
}))
|
|
162
153
|
.filter(
|
|
154
|
+
// FIXME(rlecellier): question!
|
|
155
|
+
// does that mean the we hide the enrollment when user cannot enroll?
|
|
156
|
+
// even if he's already enrolled ?
|
|
163
157
|
(data) => data.selected || data.courseRun.state.priority <= Priority.FUTURE_NOT_YET_OPEN,
|
|
164
158
|
);
|
|
165
159
|
}, [course, enrollments]);
|
|
@@ -168,13 +162,13 @@ const DashboardItemCourseEnrollingRuns = ({
|
|
|
168
162
|
return (
|
|
169
163
|
<div className="dashboard-item__course-enrolling__runs">
|
|
170
164
|
{error && <Banner message={error} type={BannerType.ERROR} />}
|
|
171
|
-
{
|
|
165
|
+
{courseRunOpenForEnrollmentList.length === 0 && (
|
|
172
166
|
<div className="dashboard-item__course-enrolling__no-runs">
|
|
173
167
|
<Icon name={IconTypeEnum.WARNING} size="small" />
|
|
174
168
|
<FormattedMessage {...messages.noCourseRunAvailable} />
|
|
175
169
|
</div>
|
|
176
170
|
)}
|
|
177
|
-
{
|
|
171
|
+
{courseRunOpenForEnrollmentList.map((data) => (
|
|
178
172
|
<DashboardItemCourseEnrollingRun
|
|
179
173
|
key={data.courseRun.id}
|
|
180
174
|
courseRun={data.courseRun}
|
|
@@ -217,6 +211,7 @@ export const DashboardItemCourseEnrollingRun = ({
|
|
|
217
211
|
}: DashboardItemCourseEnrollingRunProps) => {
|
|
218
212
|
const intl = useIntl();
|
|
219
213
|
const formatDate = useDateFormat();
|
|
214
|
+
const courseRunPeriodMessage = useCourseRunPeriodMessage(courseRun, selected);
|
|
220
215
|
const haveToSignContract = order ? orderNeedsSignature(order, product) : false;
|
|
221
216
|
const isOpenedForEnrollment = useMemo(
|
|
222
217
|
() => courseRun.state.priority < Priority.FUTURE_NOT_YET_OPEN,
|
|
@@ -239,16 +234,11 @@ export const DashboardItemCourseEnrollingRun = ({
|
|
|
239
234
|
)}
|
|
240
235
|
<strong>{courseRun.title}</strong>
|
|
241
236
|
</p>
|
|
242
|
-
|
|
243
|
-
{...(selected ? messages.enrolledRunPeriod : messages.runPeriod)}
|
|
244
|
-
values={{
|
|
245
|
-
startDate: formatDate(courseRun.start, DEFAULT_DATE_FORMAT),
|
|
246
|
-
endDate: formatDate(courseRun.end, DEFAULT_DATE_FORMAT),
|
|
247
|
-
}}
|
|
248
|
-
/>
|
|
237
|
+
{courseRunPeriodMessage}
|
|
249
238
|
</div>
|
|
250
239
|
{courseRun.state.priority === Priority.FUTURE_NOT_YET_OPEN && (
|
|
251
240
|
<div className="dashboard-item__course-enrolling__run__not-opened">
|
|
241
|
+
-{' '}
|
|
252
242
|
<FormattedMessage
|
|
253
243
|
{...messages.enrollmentNotYetOpened}
|
|
254
244
|
values={{ enrollment_start: formatDate(courseRun.enrollment_start) }}
|
|
@@ -256,18 +246,22 @@ export const DashboardItemCourseEnrollingRun = ({
|
|
|
256
246
|
</div>
|
|
257
247
|
)}
|
|
258
248
|
</div>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
249
|
+
{selected ? (
|
|
250
|
+
SHOW_ACCESS_COURSE_PRIORITIES.includes(courseRun.state.priority) && (
|
|
251
|
+
<div>
|
|
252
|
+
<Button
|
|
253
|
+
color="secondary"
|
|
254
|
+
size="small"
|
|
255
|
+
href={courseRun.resource_link}
|
|
256
|
+
data-testid="dashboard-item-enrollment__button"
|
|
257
|
+
className="dashboard-item__button"
|
|
258
|
+
>
|
|
259
|
+
<FormattedMessage {...messages.accessCourse} />
|
|
260
|
+
</Button>
|
|
261
|
+
</div>
|
|
262
|
+
)
|
|
263
|
+
) : (
|
|
264
|
+
<div>
|
|
271
265
|
<Button
|
|
272
266
|
disabled={!isOpenedForEnrollment || haveToSignContract}
|
|
273
267
|
color="tertiary"
|
|
@@ -277,8 +271,8 @@ export const DashboardItemCourseEnrollingRun = ({
|
|
|
277
271
|
>
|
|
278
272
|
<FormattedMessage {...messages.enrollRun} />
|
|
279
273
|
</Button>
|
|
280
|
-
|
|
281
|
-
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
282
276
|
</div>
|
|
283
277
|
);
|
|
284
278
|
};
|
|
@@ -313,7 +307,7 @@ const NotEnrolled = ({
|
|
|
313
307
|
);
|
|
314
308
|
};
|
|
315
309
|
|
|
316
|
-
const SHOW_ACCESS_COURSE_PRIORITIES = [
|
|
310
|
+
export const SHOW_ACCESS_COURSE_PRIORITIES = [
|
|
317
311
|
Priority.ONGOING_OPEN,
|
|
318
312
|
Priority.ARCHIVED_OPEN,
|
|
319
313
|
Priority.ONGOING_CLOSED,
|
|
@@ -327,11 +321,16 @@ export const Enrolled = ({
|
|
|
327
321
|
icon?: boolean;
|
|
328
322
|
enrollment: Enrollment;
|
|
329
323
|
}) => {
|
|
324
|
+
const courseRunPeriodMessage = useCourseRunPeriodMessage(enrollment.course_run, true);
|
|
330
325
|
return (
|
|
331
326
|
<>
|
|
332
327
|
<div className="dashboard-item__block__status">
|
|
333
328
|
{icon && <Icon name={IconTypeEnum.SCHOOL} />}
|
|
334
|
-
|
|
329
|
+
{enrollment.is_active ? (
|
|
330
|
+
courseRunPeriodMessage
|
|
331
|
+
) : (
|
|
332
|
+
<FormattedMessage {...messages.statusNotActive} />
|
|
333
|
+
)}
|
|
335
334
|
</div>
|
|
336
335
|
{SHOW_ACCESS_COURSE_PRIORITIES.includes(enrollment.course_run.state.priority) && (
|
|
337
336
|
<Button
|
|
@@ -347,21 +346,3 @@ export const Enrolled = ({
|
|
|
347
346
|
</>
|
|
348
347
|
);
|
|
349
348
|
};
|
|
350
|
-
|
|
351
|
-
const EnrolledStatus = ({ enrollment }: { enrollment: Enrollment }) => {
|
|
352
|
-
const formatDate = useDateFormat();
|
|
353
|
-
|
|
354
|
-
if (!enrollment.is_active) {
|
|
355
|
-
return <FormattedMessage {...messages.statusNotActive} />;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return (
|
|
359
|
-
<FormattedMessage
|
|
360
|
-
{...messages.enrolledRunPeriod}
|
|
361
|
-
values={{
|
|
362
|
-
startDate: formatDate(enrollment.course_run.start, DEFAULT_DATE_FORMAT),
|
|
363
|
-
endDate: formatDate(enrollment.course_run.end, DEFAULT_DATE_FORMAT),
|
|
364
|
-
}}
|
|
365
|
-
/>
|
|
366
|
-
);
|
|
367
|
-
};
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.spec.tsx
CHANGED
|
@@ -51,16 +51,13 @@ describe('<DashboardItemEnrollment/>', () => {
|
|
|
51
51
|
expect(link).toBeEnabled();
|
|
52
52
|
expect(link).toHaveAttribute('href', enrollment.course_run.resource_link);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
57
|
-
new Date(enrollment.course_run.start),
|
|
58
|
-
) +
|
|
59
|
-
' to ' +
|
|
60
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
61
|
-
new Date(enrollment.course_run.end),
|
|
62
|
-
),
|
|
54
|
+
const fromDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
55
|
+
new Date(enrollment.course_run.start),
|
|
63
56
|
);
|
|
57
|
+
const toDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
58
|
+
new Date(enrollment.course_run.end),
|
|
59
|
+
);
|
|
60
|
+
screen.getByText(`You are enrolled for this session. It's open from ${fromDate} to ${toDate}`);
|
|
64
61
|
});
|
|
65
62
|
|
|
66
63
|
it('renders a closed enrollment', () => {
|
|
@@ -80,16 +77,7 @@ describe('<DashboardItemEnrollment/>', () => {
|
|
|
80
77
|
const link = screen.getByRole('link', { name: 'Access to course' });
|
|
81
78
|
expect(link).toBeEnabled();
|
|
82
79
|
expect(link).toHaveAttribute('href', enrollment.course_run.resource_link);
|
|
83
|
-
screen.getByText(
|
|
84
|
-
'You are enrolled for the session from ' +
|
|
85
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
86
|
-
new Date(enrollment.course_run.start),
|
|
87
|
-
) +
|
|
88
|
-
' to ' +
|
|
89
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
90
|
-
new Date(enrollment.course_run.end),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
80
|
+
expect(screen.getByText(/You are enrolled for this session./)).toBeInTheDocument();
|
|
93
81
|
});
|
|
94
82
|
|
|
95
83
|
it('renders an inactive enrollment', () => {
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { Enrollment, isCertificateProduct } from 'types/Joanie';
|
|
3
|
-
import { Enrolled } from '../
|
|
3
|
+
import { Enrolled } from '../CourseEnrolling';
|
|
4
4
|
import { DashboardItem } from '..';
|
|
5
5
|
import ProductCertificateFooter from './ProductCertificateFooter';
|
|
6
6
|
|
|
@@ -16,7 +16,10 @@ import { PropsWithChildren } from 'react';
|
|
|
16
16
|
import fetchMock from 'fetch-mock';
|
|
17
17
|
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
|
|
18
18
|
import { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
CourseStateFactory,
|
|
21
|
+
RichieContextFactory as mockRichieContextFactory,
|
|
22
|
+
} from 'utils/test/factories/richie';
|
|
20
23
|
import {
|
|
21
24
|
CertificateFactory,
|
|
22
25
|
CourseLightFactory,
|
|
@@ -213,18 +216,19 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
213
216
|
await screen.findByRole('heading', { level: 5, name: product.title });
|
|
214
217
|
await screen.findByText('Ref. ' + (order.course as CourseLight).code);
|
|
215
218
|
await screen.findByText('On going');
|
|
219
|
+
const fromDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
220
|
+
new Date(order.target_enrollments[0].course_run.start),
|
|
221
|
+
);
|
|
222
|
+
const toDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
223
|
+
new Date(order.target_enrollments[0].course_run.end),
|
|
224
|
+
);
|
|
216
225
|
await resolveAll(order.target_courses, async (course) => {
|
|
217
226
|
await screen.findByRole('heading', { level: 6, name: course.title });
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
' to ' +
|
|
224
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
225
|
-
new Date(order.target_enrollments[0].course_run.end),
|
|
226
|
-
),
|
|
227
|
-
);
|
|
227
|
+
expect(
|
|
228
|
+
screen.getByText(
|
|
229
|
+
`You are enrolled for this session. It's open from ${fromDate} to ${toDate}`,
|
|
230
|
+
),
|
|
231
|
+
).toBeInTheDocument();
|
|
228
232
|
screen.getByRole('link', { name: 'Access to course' });
|
|
229
233
|
});
|
|
230
234
|
});
|
|
@@ -269,7 +273,15 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
269
273
|
|
|
270
274
|
it('renders a writable order with enrolled target course', async () => {
|
|
271
275
|
const order: CredentialOrder = CredentialOrderFactory({
|
|
272
|
-
target_courses:
|
|
276
|
+
target_courses: [
|
|
277
|
+
TargetCourseFactory({
|
|
278
|
+
course_runs: [
|
|
279
|
+
CourseRunFactory({
|
|
280
|
+
state: CourseStateFactory({ priority: Priority.ONGOING_OPEN }).one(),
|
|
281
|
+
}).one(),
|
|
282
|
+
],
|
|
283
|
+
}).one(),
|
|
284
|
+
],
|
|
273
285
|
}).one();
|
|
274
286
|
// Make target course enrolled.
|
|
275
287
|
order.target_enrollments = [
|
|
@@ -297,13 +309,17 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
297
309
|
// Expect the first courseRun to be enrolled but not the others.
|
|
298
310
|
if (i === 0) {
|
|
299
311
|
expect(queryByRole(runElement, 'button', { name: 'Enroll' })).toBeNull();
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
'You are enrolled for the session from ' +
|
|
303
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(new Date(courseRun.start)) +
|
|
304
|
-
' to ' +
|
|
305
|
-
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(new Date(courseRun.end)),
|
|
312
|
+
const fromDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
313
|
+
new Date(courseRun.start),
|
|
306
314
|
);
|
|
315
|
+
const toDate = new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
316
|
+
new Date(courseRun.end),
|
|
317
|
+
);
|
|
318
|
+
expect(
|
|
319
|
+
screen.getByText(
|
|
320
|
+
`You are enrolled for this session. It's open from ${fromDate} to ${toDate}`,
|
|
321
|
+
),
|
|
322
|
+
).toBeInTheDocument();
|
|
307
323
|
const button = getByRole(runElement, 'link', { name: 'Access to course' });
|
|
308
324
|
expect(button).toHaveAttribute('href', courseRun.resource_link);
|
|
309
325
|
} else {
|
|
@@ -323,7 +339,15 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
323
339
|
it('renders a writable order with not enrolled target course and enrolls it', async () => {
|
|
324
340
|
// Initial order without enrollment.
|
|
325
341
|
const order: CredentialOrder = CredentialOrderFactory({
|
|
326
|
-
target_courses:
|
|
342
|
+
target_courses: [
|
|
343
|
+
TargetCourseFactory({
|
|
344
|
+
course_runs: [
|
|
345
|
+
CourseRunFactory({
|
|
346
|
+
state: CourseStateFactory({ priority: Priority.ONGOING_OPEN }).one(),
|
|
347
|
+
}).one(),
|
|
348
|
+
],
|
|
349
|
+
}).one(),
|
|
350
|
+
],
|
|
327
351
|
target_enrollments: [],
|
|
328
352
|
}).one();
|
|
329
353
|
const { product } = mockCourseProductWithOrder(order);
|
|
@@ -451,7 +475,16 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
451
475
|
it('renders a writable order with enrolled target course and changes the enrollment', async () => {
|
|
452
476
|
// Initial order with first course run enrolled.
|
|
453
477
|
const order: CredentialOrder = CredentialOrderFactory({
|
|
454
|
-
target_courses:
|
|
478
|
+
target_courses: [
|
|
479
|
+
TargetCourseFactory({
|
|
480
|
+
course_runs: [
|
|
481
|
+
CourseRunFactory().one(),
|
|
482
|
+
CourseRunFactory({
|
|
483
|
+
state: CourseStateFactory({ priority: Priority.ONGOING_OPEN }).one(),
|
|
484
|
+
}).one(),
|
|
485
|
+
],
|
|
486
|
+
}).one(),
|
|
487
|
+
],
|
|
455
488
|
}).one();
|
|
456
489
|
const initialEnrolledCourseRun = order.target_courses[0].course_runs[0];
|
|
457
490
|
order.target_enrollments = EnrollmentFactory({ course_run: initialEnrolledCourseRun }).many(1);
|
|
@@ -550,7 +583,16 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
550
583
|
it('renders a writable order with enrolled target course and refuse the confirm message when enrolling', async () => {
|
|
551
584
|
// Initial order without enrollment.
|
|
552
585
|
const order: CredentialOrder = CredentialOrderFactory({
|
|
553
|
-
target_courses:
|
|
586
|
+
target_courses: [
|
|
587
|
+
TargetCourseFactory({
|
|
588
|
+
course_runs: [
|
|
589
|
+
CourseRunFactory({
|
|
590
|
+
state: CourseStateFactory({ priority: Priority.ONGOING_OPEN }).one(),
|
|
591
|
+
}).one(),
|
|
592
|
+
CourseRunFactory().one(),
|
|
593
|
+
],
|
|
594
|
+
}).one(),
|
|
595
|
+
],
|
|
554
596
|
}).one();
|
|
555
597
|
|
|
556
598
|
const initialEnrolledCourseRun = order.target_courses[0].course_runs[0];
|
|
@@ -620,11 +662,20 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
620
662
|
it('renders a writable order with non-enrolled (is_active=false) target course and changes the enrollment', async () => {
|
|
621
663
|
// Initial order with first course run enrolled.
|
|
622
664
|
const order: CredentialOrder = CredentialOrderFactory({
|
|
623
|
-
target_courses: TargetCourseFactory(
|
|
665
|
+
target_courses: TargetCourseFactory({
|
|
666
|
+
course_runs: [
|
|
667
|
+
CourseRunFactory({
|
|
668
|
+
state: CourseStateFactory({ priority: Priority.ONGOING_OPEN }).one(),
|
|
669
|
+
}).one(),
|
|
670
|
+
],
|
|
671
|
+
}).many(1),
|
|
624
672
|
}).one();
|
|
625
673
|
|
|
626
674
|
const courseRun = order.target_courses[0].course_runs[0];
|
|
627
|
-
const enrollment = EnrollmentFactory({
|
|
675
|
+
const enrollment = EnrollmentFactory({
|
|
676
|
+
course_run: courseRun,
|
|
677
|
+
is_active: false,
|
|
678
|
+
}).one();
|
|
628
679
|
order.target_enrollments = [enrollment];
|
|
629
680
|
|
|
630
681
|
// When the existing enrollment will be set as is_active: true.
|
|
@@ -725,6 +776,7 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
725
776
|
new Intl.DateTimeFormat('en', DEFAULT_DATE_FORMAT).format(
|
|
726
777
|
new Date(order.target_courses[0].course_runs[0].enrollment_start),
|
|
727
778
|
),
|
|
779
|
+
{ exact: false },
|
|
728
780
|
);
|
|
729
781
|
|
|
730
782
|
// Enroll button should be disabled.
|
|
@@ -759,7 +811,7 @@ describe('<DashboardItemOrder/>', () => {
|
|
|
759
811
|
|
|
760
812
|
// The course run should be shown as enrolled even if is it past.
|
|
761
813
|
const runElement = screen.getByTestId('dashboard-item__course-enrolling__run__' + courseRun.id);
|
|
762
|
-
|
|
814
|
+
expect(screen.queryByRole('link', { name: 'Access to course' })).not.toBeInTheDocument();
|
|
763
815
|
expect(queryByRole(runElement, 'button', { name: 'Enroll' })).toBeNull();
|
|
764
816
|
});
|
|
765
817
|
|
|
@@ -13,7 +13,7 @@ import { useCourseProduct } from 'hooks/useCourseProducts';
|
|
|
13
13
|
import { orderNeedsSignature } from 'widgets/Dashboard/components/DashboardItem/utils/order';
|
|
14
14
|
|
|
15
15
|
import { DashboardSubItemsList } from '../DashboardSubItemsList';
|
|
16
|
-
import { DashboardItemCourseEnrolling } from '../
|
|
16
|
+
import { DashboardItemCourseEnrolling } from '../CourseEnrolling';
|
|
17
17
|
import { DashboardItem } from '../index';
|
|
18
18
|
import { DashboardItemContract } from '../Contract';
|
|
19
19
|
import OrderStateMessage from './OrderStateMessage';
|