richie-education 2.28.0 → 2.28.2-dev23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/i18n/locales/fr-FR.json +10 -10
- package/js/components/AddressesManagement/AddressForm/validationSchema.spec.ts +2 -2
- package/js/components/PaymentInterfaces/LyraPopIn.tsx +10 -3
- package/js/components/PaymentInterfaces/types.ts +1 -1
- package/js/components/PurchaseButton/index.spec.tsx +9 -9
- package/js/components/PurchaseButton/index.tsx +2 -1
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +13 -3
- package/js/components/SaleTunnel/hooks/useTerms.tsx +2 -2
- package/js/components/SaleTunnel/index.credential.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process.spec.tsx +11 -4
- package/js/components/SaleTunnel/index.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.stories.tsx +3 -2
- package/js/components/SaleTunnel/index.tsx +2 -2
- package/js/translations/fr-FR.json +1 -1
- package/js/types/commonDataProps.ts +0 -1
- package/js/types/index.ts +6 -0
- package/js/utils/CourseRunHelper/index.spec.ts +35 -0
- package/js/utils/CourseRunHelper/index.ts +13 -0
- package/js/utils/react-query/useSessionQuery/index.ts +1 -1
- package/js/utils/test/factories/richie.ts +16 -2
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +35 -5
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -10
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +3 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +32 -30
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +5 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +3 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.login.spec.tsx +5 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.logout.spec.tsx +5 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.tsx +2 -2
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusAsideList/index.tsx +11 -4
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +20 -9
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +111 -0
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +311 -15
- package/js/widgets/SyllabusCourseRunsList/index.tsx +24 -8
- package/package.json +44 -44
- package/tsconfig.json +1 -1
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
findByRole,
|
|
4
4
|
getByRole,
|
|
5
5
|
getByText,
|
|
6
|
+
queryByText,
|
|
6
7
|
queryByRole,
|
|
7
8
|
screen,
|
|
8
9
|
within,
|
|
@@ -15,13 +16,14 @@ import { faker } from '@faker-js/faker';
|
|
|
15
16
|
import {
|
|
16
17
|
CourseRunFactoryFromPriority,
|
|
17
18
|
RichieContextFactory as mockRichieContextFactory,
|
|
19
|
+
PacedCourseFactory,
|
|
18
20
|
UserFactory,
|
|
19
21
|
} from 'utils/test/factories/richie';
|
|
20
22
|
import SyllabusCourseRunsList from 'widgets/SyllabusCourseRunsList/index';
|
|
21
23
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
22
24
|
import { CourseRun, Priority } from 'types';
|
|
23
25
|
import { CourseProductRelation } from 'types/Joanie';
|
|
24
|
-
import {
|
|
26
|
+
import { CourseProductRelationFactory } from 'utils/test/factories/joanie';
|
|
25
27
|
import { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
|
|
26
28
|
import { StringHelper } from 'utils/StringHelper';
|
|
27
29
|
import { computeStates } from 'utils/CourseRuns';
|
|
@@ -131,6 +133,83 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
131
133
|
});
|
|
132
134
|
};
|
|
133
135
|
|
|
136
|
+
const expectFullDates = (container: HTMLElement, courseRun: CourseRun) => {
|
|
137
|
+
[courseRun] = computeStates([courseRun]);
|
|
138
|
+
const intl = createIntl({ locale: 'en' });
|
|
139
|
+
const heading = getByRole(container, 'heading', {
|
|
140
|
+
name: courseRun.title,
|
|
141
|
+
});
|
|
142
|
+
const runContainer = heading.parentNode! as HTMLElement;
|
|
143
|
+
|
|
144
|
+
const enrollmentNode = getByText(runContainer, 'Enrollment');
|
|
145
|
+
|
|
146
|
+
const enrollmentDatesContainer = enrollmentNode.nextSibling!;
|
|
147
|
+
const enrollmentStart = intl.formatDate(
|
|
148
|
+
new Date(courseRun.enrollment_start),
|
|
149
|
+
DEFAULT_DATE_FORMAT,
|
|
150
|
+
);
|
|
151
|
+
const enrollmentEnd = intl.formatDate(new Date(courseRun.enrollment_end), DEFAULT_DATE_FORMAT);
|
|
152
|
+
expect(enrollmentDatesContainer.textContent).toEqual(
|
|
153
|
+
`From ${enrollmentStart} to ${enrollmentEnd}`,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const courseNode = enrollmentDatesContainer.nextSibling!;
|
|
157
|
+
expect(courseNode.textContent).toEqual('Course');
|
|
158
|
+
|
|
159
|
+
const start = intl.formatDate(new Date(courseRun.start), DEFAULT_DATE_FORMAT);
|
|
160
|
+
const end = intl.formatDate(new Date(courseRun.end), DEFAULT_DATE_FORMAT);
|
|
161
|
+
|
|
162
|
+
const datesContainer = courseNode.nextSibling!;
|
|
163
|
+
expect(datesContainer.textContent).toEqual(`From ${start} to ${end}`);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const expectCompactedDates = (container: HTMLElement, courseRun: CourseRun) => {
|
|
167
|
+
[courseRun] = computeStates([courseRun]);
|
|
168
|
+
const intl = createIntl({ locale: 'en' });
|
|
169
|
+
const heading = getByRole(container, 'heading', {
|
|
170
|
+
name: courseRun.title,
|
|
171
|
+
});
|
|
172
|
+
const runContainer = heading.parentNode! as HTMLElement;
|
|
173
|
+
const courseDatesText = courseRun.end
|
|
174
|
+
? `Available until ${intl.formatDate(new Date(courseRun.end), DEFAULT_DATE_FORMAT)}`
|
|
175
|
+
: `Available`;
|
|
176
|
+
|
|
177
|
+
const courseDatesContainer = getByText(runContainer, courseDatesText);
|
|
178
|
+
expect(courseDatesContainer).not.toBeNull();
|
|
179
|
+
|
|
180
|
+
getByRole(runContainer, 'link', {
|
|
181
|
+
name: StringHelper.capitalizeFirst(courseRun.state.call_to_action)!,
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const expectLanguageVisibility = (
|
|
186
|
+
container: HTMLElement,
|
|
187
|
+
courseRun: CourseRun,
|
|
188
|
+
isLanguagesVisible: boolean,
|
|
189
|
+
) => {
|
|
190
|
+
[courseRun] = computeStates([courseRun]);
|
|
191
|
+
const intl = createIntl({ locale: 'en' });
|
|
192
|
+
const heading = getByRole(container, 'heading', {
|
|
193
|
+
name: courseRun.title,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const runContainer = heading.parentNode! as HTMLElement;
|
|
197
|
+
|
|
198
|
+
const languagesNode = queryByText(runContainer, 'Languages');
|
|
199
|
+
if (isLanguagesVisible) {
|
|
200
|
+
expect(languagesNode).not.toBeNull();
|
|
201
|
+
|
|
202
|
+
const languagesContainer = languagesNode?.nextSibling! as HTMLElement;
|
|
203
|
+
getByText(languagesContainer, IntlHelper.getLocalizedLanguages(courseRun.languages, intl));
|
|
204
|
+
} else {
|
|
205
|
+
expect(languagesNode).toBeNull();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getByRole(runContainer, 'link', {
|
|
209
|
+
name: StringHelper.capitalizeFirst(courseRun.state.call_to_action)!,
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
134
213
|
const expectCourseProduct = async (container: HTMLElement, relation: CourseProductRelation) => {
|
|
135
214
|
const heading = await findByRole(container, 'heading', {
|
|
136
215
|
name: relation.product.title,
|
|
@@ -145,7 +224,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
145
224
|
};
|
|
146
225
|
|
|
147
226
|
it('has no opened course run', async () => {
|
|
148
|
-
const course =
|
|
227
|
+
const course = PacedCourseFactory().one();
|
|
149
228
|
const courseRuns = [
|
|
150
229
|
CourseRunFactoryFromPriority(Priority.FUTURE_NOT_YET_OPEN)().one(),
|
|
151
230
|
CourseRunFactoryFromPriority(Priority.FUTURE_CLOSED)({
|
|
@@ -182,7 +261,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
182
261
|
});
|
|
183
262
|
|
|
184
263
|
it('has one opened course run', async () => {
|
|
185
|
-
const course =
|
|
264
|
+
const course = PacedCourseFactory().one();
|
|
186
265
|
const courseRuns = [
|
|
187
266
|
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one(),
|
|
188
267
|
CourseRunFactoryFromPriority(Priority.FUTURE_CLOSED)({
|
|
@@ -221,7 +300,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
221
300
|
});
|
|
222
301
|
|
|
223
302
|
it('has one forever open course run', async () => {
|
|
224
|
-
const course =
|
|
303
|
+
const course = PacedCourseFactory().one();
|
|
225
304
|
const startDate = faker.date.past();
|
|
226
305
|
const enrollmentStartDate = faker.date.past();
|
|
227
306
|
const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
@@ -263,7 +342,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
263
342
|
);
|
|
264
343
|
});
|
|
265
344
|
it('has multiple opened course run', async () => {
|
|
266
|
-
const course =
|
|
345
|
+
const course = PacedCourseFactory().one();
|
|
267
346
|
const courseRuns = [
|
|
268
347
|
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one(),
|
|
269
348
|
CourseRunFactoryFromPriority(Priority.FUTURE_OPEN)().one(),
|
|
@@ -302,7 +381,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
302
381
|
});
|
|
303
382
|
|
|
304
383
|
it('has one opened product', async () => {
|
|
305
|
-
const course =
|
|
384
|
+
const course = PacedCourseFactory().one();
|
|
306
385
|
const relation = CourseProductRelationFactory().one();
|
|
307
386
|
const resourceLink = `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${relation.product.id}/`;
|
|
308
387
|
fetchMock.get(resourceLink, relation);
|
|
@@ -333,7 +412,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
333
412
|
});
|
|
334
413
|
|
|
335
414
|
it('renders a specific title in portal when there is one opened course run', () => {
|
|
336
|
-
const course =
|
|
415
|
+
const course = PacedCourseFactory().one();
|
|
337
416
|
|
|
338
417
|
render(
|
|
339
418
|
<SyllabusCourseRunsList
|
|
@@ -356,7 +435,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
356
435
|
});
|
|
357
436
|
|
|
358
437
|
it('renders a specific title in portal when there are multiple opened course run', () => {
|
|
359
|
-
const course =
|
|
438
|
+
const course = PacedCourseFactory().one();
|
|
360
439
|
render(
|
|
361
440
|
<SyllabusCourseRunsList
|
|
362
441
|
courseRuns={[
|
|
@@ -381,7 +460,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
381
460
|
});
|
|
382
461
|
|
|
383
462
|
it('renders different categories correctly', async () => {
|
|
384
|
-
const course =
|
|
463
|
+
const course = PacedCourseFactory().one();
|
|
385
464
|
const courseRuns = [
|
|
386
465
|
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one(),
|
|
387
466
|
CourseRunFactoryFromPriority(Priority.FUTURE_OPEN)().one(),
|
|
@@ -445,7 +524,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
445
524
|
});
|
|
446
525
|
|
|
447
526
|
it('renders an opened course run with an existing LMS Backend', () => {
|
|
448
|
-
const course =
|
|
527
|
+
const course = PacedCourseFactory().one();
|
|
449
528
|
const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one();
|
|
450
529
|
courseRun.resource_link = 'https://openedx.endpoint' + courseRun.resource_link;
|
|
451
530
|
|
|
@@ -472,7 +551,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
472
551
|
});
|
|
473
552
|
|
|
474
553
|
it('limits the amount of archived course runs displayed', async () => {
|
|
475
|
-
const course =
|
|
554
|
+
const course = PacedCourseFactory().one();
|
|
476
555
|
const courseRuns = [...Array(MAX_ARCHIVED_COURSE_RUNS * 2)]
|
|
477
556
|
.map(() => CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)().one())
|
|
478
557
|
.sort((a, b) => Date.parse(a.start) - Date.parse(b.start));
|
|
@@ -516,7 +595,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
516
595
|
});
|
|
517
596
|
|
|
518
597
|
it('does not limit the amount of archived course runs displayed', async () => {
|
|
519
|
-
const course =
|
|
598
|
+
const course = PacedCourseFactory().one();
|
|
520
599
|
|
|
521
600
|
const courseRuns = [...Array(MAX_ARCHIVED_COURSE_RUNS - 1)]
|
|
522
601
|
.map(() => CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)().one())
|
|
@@ -545,7 +624,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
545
624
|
});
|
|
546
625
|
|
|
547
626
|
it('renders opened runs with the same locale as the user above', async () => {
|
|
548
|
-
const course =
|
|
627
|
+
const course = PacedCourseFactory().one();
|
|
549
628
|
|
|
550
629
|
const refDate = faker.date.future();
|
|
551
630
|
const futureDate = (days: number) => {
|
|
@@ -614,8 +693,225 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
614
693
|
expectCourseRunOpened(elements[4], courseRuns[4]);
|
|
615
694
|
});
|
|
616
695
|
|
|
696
|
+
it('renders instructor pace opened run with same languages', async () => {
|
|
697
|
+
const course = PacedCourseFactory({ is_self_paced: false }).one();
|
|
698
|
+
|
|
699
|
+
const courseRuns: CourseRun[] = [
|
|
700
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
701
|
+
languages: ['en'],
|
|
702
|
+
}).one(),
|
|
703
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
704
|
+
languages: ['en'],
|
|
705
|
+
}).one(),
|
|
706
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
707
|
+
languages: ['en'],
|
|
708
|
+
}).one(),
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
render(
|
|
712
|
+
<SyllabusCourseRunsList
|
|
713
|
+
courseRuns={courseRuns}
|
|
714
|
+
course={course}
|
|
715
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
716
|
+
/>,
|
|
717
|
+
{
|
|
718
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
719
|
+
},
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
// Header of the opened run.
|
|
723
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
724
|
+
1,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
// Assert that the run displays the extended dates.
|
|
728
|
+
expectFullDates(getHeaderContainer(), courseRuns[0]);
|
|
729
|
+
|
|
730
|
+
// Assert that the run does not display the containers related to languages.
|
|
731
|
+
expectLanguageVisibility(getHeaderContainer(), courseRuns[0], false);
|
|
732
|
+
|
|
733
|
+
const portalContainer = getPortalContainer();
|
|
734
|
+
|
|
735
|
+
// Expect that all closed course runs to be present.
|
|
736
|
+
courseRuns.slice(1).forEach((run) => expectCourseRunInList(portalContainer, run));
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('renders instructor pace opened run with different languages', async () => {
|
|
740
|
+
const course = PacedCourseFactory({ is_self_paced: false }).one();
|
|
741
|
+
|
|
742
|
+
const courseRuns: CourseRun[] = [
|
|
743
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
744
|
+
languages: ['en'],
|
|
745
|
+
}).one(),
|
|
746
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
747
|
+
languages: ['it'],
|
|
748
|
+
}).one(),
|
|
749
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
750
|
+
languages: ['fr'],
|
|
751
|
+
}).one(),
|
|
752
|
+
];
|
|
753
|
+
|
|
754
|
+
render(
|
|
755
|
+
<SyllabusCourseRunsList
|
|
756
|
+
courseRuns={courseRuns}
|
|
757
|
+
course={course}
|
|
758
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
759
|
+
/>,
|
|
760
|
+
{
|
|
761
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
762
|
+
},
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
// Header of the opened run.
|
|
766
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
767
|
+
1,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Assert that the run displays the extended dates.
|
|
771
|
+
expectFullDates(getHeaderContainer(), courseRuns[0]);
|
|
772
|
+
|
|
773
|
+
// Assert that the run displays its languages.
|
|
774
|
+
expectLanguageVisibility(getHeaderContainer(), courseRuns[0], true);
|
|
775
|
+
|
|
776
|
+
const portalContainer = getPortalContainer();
|
|
777
|
+
|
|
778
|
+
// Expect that all closed course runs to be present.
|
|
779
|
+
courseRuns.slice(1).forEach((run) => expectCourseRunInList(portalContainer, run));
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('renders self-paced opened run with different languages', async () => {
|
|
783
|
+
const course = PacedCourseFactory({ is_self_paced: true }).one();
|
|
784
|
+
|
|
785
|
+
const courseRuns = [
|
|
786
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
787
|
+
languages: ['it'],
|
|
788
|
+
}).one(),
|
|
789
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
790
|
+
languages: ['en'],
|
|
791
|
+
}).one(),
|
|
792
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
793
|
+
languages: ['fr'],
|
|
794
|
+
}).one(),
|
|
795
|
+
];
|
|
796
|
+
|
|
797
|
+
render(
|
|
798
|
+
<SyllabusCourseRunsList
|
|
799
|
+
courseRuns={courseRuns}
|
|
800
|
+
course={course}
|
|
801
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
802
|
+
/>,
|
|
803
|
+
{
|
|
804
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
805
|
+
},
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
// Header of the opened run.
|
|
809
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
810
|
+
1,
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
// Assert that the run displays the containers related to the self-paced run dates.
|
|
814
|
+
expectCompactedDates(getHeaderContainer(), courseRuns[0]);
|
|
815
|
+
|
|
816
|
+
// Assert that the run displays the containers related to languages.
|
|
817
|
+
expectLanguageVisibility(getHeaderContainer(), courseRuns[0], true);
|
|
818
|
+
|
|
819
|
+
const portalContainer = getPortalContainer();
|
|
820
|
+
|
|
821
|
+
// Expect that all closed course runs to be present.
|
|
822
|
+
courseRuns.slice(1).forEach((run) => expectCourseRunInList(portalContainer, run));
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it('renders self-paced opened run with same languages', async () => {
|
|
826
|
+
const course = PacedCourseFactory({ is_self_paced: true }).one();
|
|
827
|
+
|
|
828
|
+
const courseRuns: CourseRun[] = [
|
|
829
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
830
|
+
languages: ['en'],
|
|
831
|
+
}).one(),
|
|
832
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
833
|
+
languages: ['en'],
|
|
834
|
+
}).one(),
|
|
835
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
836
|
+
languages: ['en'],
|
|
837
|
+
}).one(),
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
render(
|
|
841
|
+
<SyllabusCourseRunsList
|
|
842
|
+
courseRuns={courseRuns}
|
|
843
|
+
course={course}
|
|
844
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
845
|
+
/>,
|
|
846
|
+
{
|
|
847
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
848
|
+
},
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
// Header of the opened run.
|
|
852
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
853
|
+
1,
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
// Assert that the run displays the simplified version of dates
|
|
857
|
+
expectCompactedDates(getHeaderContainer(), courseRuns[0]);
|
|
858
|
+
|
|
859
|
+
// Assert that the run doesn't display the languages
|
|
860
|
+
expectLanguageVisibility(getHeaderContainer(), courseRuns[0], false);
|
|
861
|
+
|
|
862
|
+
const portalContainer = getPortalContainer();
|
|
863
|
+
|
|
864
|
+
// Expect that all closed course runs to be present.
|
|
865
|
+
courseRuns.slice(1).forEach((run) => expectCourseRunInList(portalContainer, run));
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('renders self-paced opened forever run with same languages', async () => {
|
|
869
|
+
const course = PacedCourseFactory({ is_self_paced: true }).one();
|
|
870
|
+
|
|
871
|
+
const courseRuns: CourseRun[] = [
|
|
872
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
873
|
+
languages: ['en'],
|
|
874
|
+
end: undefined,
|
|
875
|
+
}).one(),
|
|
876
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
877
|
+
languages: ['en'],
|
|
878
|
+
}).one(),
|
|
879
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
880
|
+
languages: ['en'],
|
|
881
|
+
}).one(),
|
|
882
|
+
];
|
|
883
|
+
|
|
884
|
+
render(
|
|
885
|
+
<SyllabusCourseRunsList
|
|
886
|
+
courseRuns={courseRuns}
|
|
887
|
+
course={course}
|
|
888
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
889
|
+
/>,
|
|
890
|
+
{
|
|
891
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
892
|
+
},
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// Header of the opened run.
|
|
896
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
897
|
+
1,
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
// Assert that the run displays the simplified version of dates,
|
|
901
|
+
// with hidden course end date.
|
|
902
|
+
expectCompactedDates(getHeaderContainer(), courseRuns[0]);
|
|
903
|
+
|
|
904
|
+
// Assert that the run doesn't display the languages
|
|
905
|
+
expectLanguageVisibility(getHeaderContainer(), courseRuns[0], false);
|
|
906
|
+
|
|
907
|
+
const portalContainer = getPortalContainer();
|
|
908
|
+
|
|
909
|
+
// Expect that all closed course runs to be present.
|
|
910
|
+
courseRuns.slice(1).forEach((run) => expectCourseRunInList(portalContainer, run));
|
|
911
|
+
});
|
|
912
|
+
|
|
617
913
|
it('renders course runs with snapshot link if needed', async () => {
|
|
618
|
-
const course =
|
|
914
|
+
const course = PacedCourseFactory().one();
|
|
619
915
|
const refDate = faker.date.past();
|
|
620
916
|
const pastDate = (days: number) => {
|
|
621
917
|
const date = new Date(refDate.getTime());
|
|
@@ -661,7 +957,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
661
957
|
|
|
662
958
|
it('renders ongoing course runs with enrollment information', async () => {
|
|
663
959
|
const user = UserFactory().one();
|
|
664
|
-
const course =
|
|
960
|
+
const course = PacedCourseFactory().one();
|
|
665
961
|
|
|
666
962
|
const onGoingCourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_CLOSED)({
|
|
667
963
|
title: 'Ongoing course run',
|
|
@@ -2,14 +2,15 @@ import React, { useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { Button, CunninghamProvider } from '@openfun/cunningham-react';
|
|
5
|
-
import { CourseRun, Priority } from 'types';
|
|
5
|
+
import { PacedCourse, CourseRun, Priority } from 'types';
|
|
6
6
|
import { computeStates } from 'utils/CourseRuns';
|
|
7
|
+
import { CourseRunHelper } from 'utils/CourseRunHelper';
|
|
7
8
|
import { SyllabusAsideList } from 'widgets/SyllabusCourseRunsList/components/SyllabusAsideList';
|
|
9
|
+
import { SyllabusCourseRunCompacted } from 'widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted';
|
|
8
10
|
import { SyllabusCourseRun } from 'widgets/SyllabusCourseRunsList/components/SyllabusCourseRun';
|
|
9
11
|
import { DjangoCMSPluginsInit } from 'components/DjangoCMSTemplate';
|
|
10
12
|
import { isJoanieEnabled } from 'api/joanie';
|
|
11
13
|
import context from 'utils/context';
|
|
12
|
-
import { CourseLight } from 'types/Joanie';
|
|
13
14
|
import CourseWishButton from './components/CourseWishButton';
|
|
14
15
|
|
|
15
16
|
const OPENED_COURSES_ELEMENT_ID = 'courseDetailsRunsOpen';
|
|
@@ -41,7 +42,7 @@ const SyllabusCourseRunsList = ({
|
|
|
41
42
|
maxArchivedCourseRuns,
|
|
42
43
|
}: {
|
|
43
44
|
courseRuns: CourseRun[];
|
|
44
|
-
course:
|
|
45
|
+
course: PacedCourse;
|
|
45
46
|
maxArchivedCourseRuns: number;
|
|
46
47
|
}) => {
|
|
47
48
|
useEffect(() => {
|
|
@@ -60,6 +61,8 @@ const SyllabusCourseRunsList = ({
|
|
|
60
61
|
);
|
|
61
62
|
}, [courseRunsComputed]);
|
|
62
63
|
|
|
64
|
+
const runsWithSameLanguages = CourseRunHelper.IsAllCourseRunsWithSameLanguages(courseRuns);
|
|
65
|
+
|
|
63
66
|
const choose = (e: React.MouseEvent) => {
|
|
64
67
|
e.preventDefault();
|
|
65
68
|
document
|
|
@@ -81,11 +84,24 @@ const SyllabusCourseRunsList = ({
|
|
|
81
84
|
</div>
|
|
82
85
|
</div>
|
|
83
86
|
)}
|
|
84
|
-
{openedRuns.length === 1 &&
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
{openedRuns.length === 1 &&
|
|
88
|
+
(course.is_self_paced && openedRuns[0].state.priority !== Priority.ARCHIVED_OPEN ? (
|
|
89
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
90
|
+
<SyllabusCourseRunCompacted
|
|
91
|
+
courseRun={openedRuns[0]}
|
|
92
|
+
course={course}
|
|
93
|
+
showLanguages={runsWithSameLanguages}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
) : (
|
|
97
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
98
|
+
<SyllabusCourseRun
|
|
99
|
+
courseRun={openedRuns[0]}
|
|
100
|
+
course={course}
|
|
101
|
+
showLanguages={runsWithSameLanguages}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
89
105
|
{openedRuns.length > 1 && (
|
|
90
106
|
<div className="course-detail__row course-detail__runs course-detail__go-to-open-runs">
|
|
91
107
|
<p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "2.28.
|
|
3
|
+
"version": "2.28.2-dev23",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -38,34 +38,34 @@
|
|
|
38
38
|
"not dead"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@babel/core": "7.24.
|
|
41
|
+
"@babel/core": "7.24.9",
|
|
42
42
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
|
43
|
-
"@babel/plugin-transform-modules-commonjs": "7.24.
|
|
44
|
-
"@babel/preset-env": "7.
|
|
43
|
+
"@babel/plugin-transform-modules-commonjs": "7.24.8",
|
|
44
|
+
"@babel/preset-env": "7.25.0",
|
|
45
45
|
"@babel/preset-react": "7.24.7",
|
|
46
46
|
"@babel/preset-typescript": "7.24.7",
|
|
47
47
|
"@faker-js/faker": "8.4.1",
|
|
48
48
|
"@formatjs/cli": "6.2.12",
|
|
49
49
|
"@formatjs/intl-relativetimeformat": "11.2.14",
|
|
50
|
-
"@hookform/resolvers": "3.
|
|
50
|
+
"@hookform/resolvers": "3.9.0",
|
|
51
51
|
"@lyracom/embedded-form-glue": "1.4.2",
|
|
52
52
|
"@openfun/cunningham-react": "2.9.3",
|
|
53
53
|
"@openfun/cunningham-tokens": "2.1.1",
|
|
54
|
-
"@sentry/browser": "8.
|
|
55
|
-
"@sentry/types": "8.
|
|
56
|
-
"@storybook/addon-actions": "8.
|
|
57
|
-
"@storybook/addon-essentials": "8.
|
|
58
|
-
"@storybook/addon-interactions": "8.
|
|
59
|
-
"@storybook/addon-links": "8.
|
|
60
|
-
"@storybook/react": "8.
|
|
61
|
-
"@storybook/react-webpack5": "8.
|
|
62
|
-
"@storybook/test": "8.
|
|
63
|
-
"@tanstack/query-core": "5.
|
|
64
|
-
"@tanstack/query-sync-storage-persister": "5.
|
|
65
|
-
"@tanstack/react-query": "5.
|
|
66
|
-
"@tanstack/react-query-persist-client": "5.
|
|
67
|
-
"@testing-library/dom": "10.
|
|
68
|
-
"@testing-library/jest-dom": "6.4.
|
|
54
|
+
"@sentry/browser": "8.20.0",
|
|
55
|
+
"@sentry/types": "8.20.0",
|
|
56
|
+
"@storybook/addon-actions": "8.2.6",
|
|
57
|
+
"@storybook/addon-essentials": "8.2.6",
|
|
58
|
+
"@storybook/addon-interactions": "8.2.6",
|
|
59
|
+
"@storybook/addon-links": "8.2.6",
|
|
60
|
+
"@storybook/react": "8.2.6",
|
|
61
|
+
"@storybook/react-webpack5": "8.2.6",
|
|
62
|
+
"@storybook/test": "8.2.6",
|
|
63
|
+
"@tanstack/query-core": "5.51.15",
|
|
64
|
+
"@tanstack/query-sync-storage-persister": "5.51.15",
|
|
65
|
+
"@tanstack/react-query": "5.51.15",
|
|
66
|
+
"@tanstack/react-query-persist-client": "5.51.15",
|
|
67
|
+
"@testing-library/dom": "10.4.0",
|
|
68
|
+
"@testing-library/jest-dom": "6.4.8",
|
|
69
69
|
"@testing-library/react": "16.0.0",
|
|
70
70
|
"@testing-library/user-event": "14.5.2",
|
|
71
71
|
"@types/fetch-mock": "7.3.8",
|
|
@@ -79,9 +79,9 @@
|
|
|
79
79
|
"@types/react-autosuggest": "10.1.11",
|
|
80
80
|
"@types/react-dom": "18.3.0",
|
|
81
81
|
"@types/react-modal": "3.16.3",
|
|
82
|
-
"@types/uuid": "
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "7.
|
|
84
|
-
"@typescript-eslint/parser": "7.
|
|
82
|
+
"@types/uuid": "10.0.0",
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "7.17.0",
|
|
84
|
+
"@typescript-eslint/parser": "7.17.0",
|
|
85
85
|
"babel-jest": "29.7.0",
|
|
86
86
|
"babel-loader": "9.1.3",
|
|
87
87
|
"babel-plugin-react-intl": "8.2.25",
|
|
@@ -89,50 +89,50 @@
|
|
|
89
89
|
"classnames": "2.5.1",
|
|
90
90
|
"cljs-merge": "1.1.1",
|
|
91
91
|
"core-js": "3.37.1",
|
|
92
|
-
"downshift": "9.0.
|
|
92
|
+
"downshift": "9.0.7",
|
|
93
93
|
"eslint": ">=8.57.0 <9",
|
|
94
94
|
"eslint-config-airbnb": "19.0.4",
|
|
95
95
|
"eslint-config-airbnb-typescript": "18.0.0",
|
|
96
96
|
"eslint-config-prettier": "9.1.0",
|
|
97
97
|
"eslint-import-resolver-webpack": "0.13.8",
|
|
98
|
-
"eslint-plugin-compat": "
|
|
98
|
+
"eslint-plugin-compat": "6.0.0",
|
|
99
99
|
"eslint-plugin-formatjs": "4.13.3",
|
|
100
100
|
"eslint-plugin-import": "2.29.1",
|
|
101
|
-
"eslint-plugin-jsx-a11y": "6.
|
|
102
|
-
"eslint-plugin-prettier": "5.1
|
|
103
|
-
"eslint-plugin-react": "7.
|
|
101
|
+
"eslint-plugin-jsx-a11y": "6.9.0",
|
|
102
|
+
"eslint-plugin-prettier": "5.2.1",
|
|
103
|
+
"eslint-plugin-react": "7.35.0",
|
|
104
104
|
"eslint-plugin-react-hooks": "4.6.2",
|
|
105
105
|
"eslint-plugin-storybook": "0.8.0",
|
|
106
|
-
"fetch-mock": "
|
|
106
|
+
"fetch-mock": "<10",
|
|
107
107
|
"file-loader": "6.2.0",
|
|
108
|
-
"glob": "
|
|
109
|
-
"i18n-iso-countries": "7.11.
|
|
110
|
-
"iframe-resizer": "4.4.
|
|
108
|
+
"glob": "11.0.0",
|
|
109
|
+
"i18n-iso-countries": "7.11.3",
|
|
110
|
+
"iframe-resizer": "4.4.5",
|
|
111
111
|
"intl-pluralrules": "2.0.1",
|
|
112
112
|
"jest": "29.7.0",
|
|
113
113
|
"jest-environment-jsdom": "29.7.0",
|
|
114
114
|
"js-cookie": "3.0.5",
|
|
115
115
|
"lodash-es": "4.17.21",
|
|
116
116
|
"mdn-polyfills": "5.20.0",
|
|
117
|
-
"msw": "2.3.
|
|
117
|
+
"msw": "2.3.4",
|
|
118
118
|
"node-fetch": ">2.6.6 <3",
|
|
119
|
-
"nodemon": "3.1.
|
|
120
|
-
"prettier": "3.3.
|
|
121
|
-
"query-string": "9.
|
|
119
|
+
"nodemon": "3.1.4",
|
|
120
|
+
"prettier": "3.3.3",
|
|
121
|
+
"query-string": "9.1.0",
|
|
122
122
|
"react": "18.3.1",
|
|
123
123
|
"react-autosuggest": "10.1.0",
|
|
124
124
|
"react-dom": "18.3.1",
|
|
125
|
-
"react-hook-form": "7.
|
|
125
|
+
"react-hook-form": "7.52.1",
|
|
126
126
|
"react-intl": "6.6.8",
|
|
127
127
|
"react-modal": "3.16.1",
|
|
128
|
-
"react-router-dom": "6.
|
|
129
|
-
"sass": "1.77.
|
|
128
|
+
"react-router-dom": "6.25.1",
|
|
129
|
+
"sass": "1.77.8",
|
|
130
130
|
"source-map-loader": "5.0.0",
|
|
131
|
-
"storybook": "8.
|
|
131
|
+
"storybook": "8.2.6",
|
|
132
132
|
"tsconfig-paths-webpack-plugin": "4.1.0",
|
|
133
|
-
"typescript": "5.4
|
|
133
|
+
"typescript": "5.5.4",
|
|
134
134
|
"uuid": "10.0.0",
|
|
135
|
-
"webpack": "5.
|
|
135
|
+
"webpack": "5.93.0",
|
|
136
136
|
"webpack-cli": "5.1.4",
|
|
137
137
|
"whatwg-fetch": "3.6.20",
|
|
138
138
|
"xhr-mock": "2.5.1",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"yup": "1.4.0"
|
|
141
141
|
},
|
|
142
142
|
"resolutions": {
|
|
143
|
-
"@testing-library/dom": "10.
|
|
143
|
+
"@testing-library/dom": "10.4.0",
|
|
144
144
|
"@types/react": "18.3.3",
|
|
145
145
|
"@types/react-dom": "18.3.0"
|
|
146
146
|
},
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"node": "20.11.0"
|
|
152
152
|
},
|
|
153
153
|
"devDependencies": {
|
|
154
|
-
"@storybook/addon-mdx-gfm": "8.
|
|
154
|
+
"@storybook/addon-mdx-gfm": "8.2.6",
|
|
155
155
|
"@storybook/addon-webpack5-compiler-babel": "3.0.3"
|
|
156
156
|
}
|
|
157
157
|
}
|