richie-education 2.34.1-dev2 → 2.34.1-dev21
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/ContractFrame/AbstractContractFrame.spec.tsx +3 -5
- package/js/hooks/useCreditCards/index.spec.tsx +3 -2
- package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.spec.tsx +1 -1
- package/js/types/index.ts +5 -0
- package/js/utils/test/factories/richie.ts +25 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +6 -4
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +107 -0
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +107 -0
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +450 -5
- package/package.json +59 -59
- package/scss/colors/_theme.scss +8 -0
- package/scss/components/_header.scss +103 -14
- package/scss/components/_styleguide.scss +2 -1
- package/scss/objects/_selector.scss +1 -0
|
@@ -27,7 +27,7 @@ jest.mock('settings', () => ({
|
|
|
27
27
|
CONTRACT_SETTINGS: {
|
|
28
28
|
...jest.requireActual('settings').CONTRACT_SETTINGS,
|
|
29
29
|
pollInterval: 10,
|
|
30
|
-
dummySignatureSignTimeout:
|
|
30
|
+
dummySignatureSignTimeout: 50,
|
|
31
31
|
},
|
|
32
32
|
}));
|
|
33
33
|
|
|
@@ -204,12 +204,10 @@ describe('<AbstractContractFrame />', () => {
|
|
|
204
204
|
await user.click(button);
|
|
205
205
|
|
|
206
206
|
// The dummy interface should be loading
|
|
207
|
-
screen.
|
|
207
|
+
await screen.findByRole('heading', { name: 'Signing the contract ...' });
|
|
208
208
|
|
|
209
209
|
// Then the signature check polling should be started
|
|
210
|
-
await
|
|
211
|
-
expect(screen.getByRole('heading', { name: 'Verifying signature ...' })).toBeInTheDocument();
|
|
212
|
-
});
|
|
210
|
+
await screen.findByRole('heading', { name: 'Verifying signature ...' });
|
|
213
211
|
expect(
|
|
214
212
|
screen.getByText(
|
|
215
213
|
'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
|
|
@@ -208,8 +208,9 @@ describe('useCreditCards', () => {
|
|
|
208
208
|
await act(async () => {
|
|
209
209
|
responseDeferred.resolve({});
|
|
210
210
|
});
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
await waitFor(() => {
|
|
212
|
+
expect(result.current.states.updating).toBe(false);
|
|
213
|
+
});
|
|
213
214
|
expect(result.current.states.isPending).toBe(false);
|
|
214
215
|
expect(result.current.states.error).toBe(undefined);
|
|
215
216
|
});
|
|
@@ -181,7 +181,7 @@ describe('<DahsboardEditCreditCard/>', () => {
|
|
|
181
181
|
await screen.findByText('Credit cards');
|
|
182
182
|
|
|
183
183
|
// The title is correctly updated.
|
|
184
|
-
screen.
|
|
184
|
+
await screen.findByRole('heading', {
|
|
185
185
|
level: 6,
|
|
186
186
|
name: creditCardUpdated.title,
|
|
187
187
|
});
|
package/js/types/index.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface CourseRun {
|
|
|
35
35
|
title?: string;
|
|
36
36
|
snapshot?: string;
|
|
37
37
|
display_mode: CourseRunDisplayMode;
|
|
38
|
+
price?: number;
|
|
39
|
+
price_currency?: string;
|
|
40
|
+
offer?: string;
|
|
41
|
+
certificate_price?: number;
|
|
42
|
+
certificate_offer?: string;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export enum Priority {
|
|
@@ -45,7 +45,27 @@ export const CourseStateFutureOpenFactory = factory<CourseState>(() => {
|
|
|
45
45
|
};
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
enum OfferType {
|
|
49
|
+
PAID = 'PAID',
|
|
50
|
+
FREE = 'FREE',
|
|
51
|
+
PARTIALLY_FREE = 'PARTIALLY_FREE',
|
|
52
|
+
SUBSCRIPTION = 'SUBSCRIPTION',
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
export const CourseRunFactory = factory<CourseRun>(() => {
|
|
56
|
+
const offerValues = Object.values(OfferType);
|
|
57
|
+
const offer = offerValues[Math.floor(Math.random() * offerValues.length)];
|
|
58
|
+
const certificateOfferValues = [OfferType.PAID, OfferType.FREE, OfferType.SUBSCRIPTION];
|
|
59
|
+
const certificateOffer =
|
|
60
|
+
certificateOfferValues[Math.floor(Math.random() * certificateOfferValues.length)];
|
|
61
|
+
const currency = faker.finance.currency().code;
|
|
62
|
+
const price = [OfferType.FREE, OfferType.PARTIALLY_FREE].includes(offer)
|
|
63
|
+
? 0
|
|
64
|
+
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
|
|
65
|
+
const certificatePrice =
|
|
66
|
+
certificateOffer === OfferType.FREE
|
|
67
|
+
? 0
|
|
68
|
+
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
|
|
49
69
|
return {
|
|
50
70
|
id: faker.number.int(),
|
|
51
71
|
resource_link: FactoryHelper.unique(faker.internet.url),
|
|
@@ -58,6 +78,11 @@ export const CourseRunFactory = factory<CourseRun>(() => {
|
|
|
58
78
|
dashboard_link: null,
|
|
59
79
|
title: faker.lorem.sentence(3),
|
|
60
80
|
display_mode: CourseRunDisplayMode.DETAILED,
|
|
81
|
+
price,
|
|
82
|
+
price_currency: currency,
|
|
83
|
+
offer,
|
|
84
|
+
certificate_price: certificatePrice,
|
|
85
|
+
certificate_offer: certificateOffer,
|
|
61
86
|
};
|
|
62
87
|
});
|
|
63
88
|
|
|
@@ -139,11 +139,12 @@ const EnrollableCourseRunList = ({ courseRuns, order }: Props) => {
|
|
|
139
139
|
<ol className="course-runs-list">
|
|
140
140
|
{Children.toArray(
|
|
141
141
|
courseRuns.map((courseRun) => (
|
|
142
|
-
<li className="course-runs-item form-field">
|
|
142
|
+
<li key={`${order.id}|${courseRun.id}`} className="course-runs-item form-field">
|
|
143
143
|
<input
|
|
144
144
|
className="form-field__radio-input"
|
|
145
145
|
type="radio"
|
|
146
146
|
id={`${order.id}|${courseRun.id}`}
|
|
147
|
+
data-testid={`radio-input-${order.id}-${courseRun.id}`}
|
|
147
148
|
name={order.id}
|
|
148
149
|
disabled={needsSignature}
|
|
149
150
|
aria-label={intl.formatMessage(messages.ariaSelectCourseRun, {
|
|
@@ -166,11 +166,13 @@ describe('CourseProductCourseRuns', () => {
|
|
|
166
166
|
);
|
|
167
167
|
|
|
168
168
|
// - A radio input
|
|
169
|
-
screen.
|
|
170
|
-
|
|
169
|
+
const $input = screen.getByTestId(`radio-input-${order.id}-${courseRun.id}`);
|
|
170
|
+
expect($input).toHaveAttribute('type', 'radio');
|
|
171
|
+
expect($input).toHaveAccessibleName(
|
|
172
|
+
`Select course run from ${dateFormatter.format(
|
|
171
173
|
new Date(courseRun.start),
|
|
172
174
|
)} to ${dateFormatter.format(new Date(courseRun.end))}.`,
|
|
173
|
-
|
|
175
|
+
);
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
// A call to action should be displayed
|
|
@@ -215,7 +217,7 @@ describe('CourseProductCourseRuns', () => {
|
|
|
215
217
|
});
|
|
216
218
|
|
|
217
219
|
// A spinner should be displayed
|
|
218
|
-
screen.
|
|
220
|
+
await screen.findByRole('status', { name: 'Enrolling...' });
|
|
219
221
|
|
|
220
222
|
await act(async () => {
|
|
221
223
|
enrollmentDeferred.resolve(HttpStatusCode.OK);
|
|
@@ -46,6 +46,51 @@ const messages = defineMessages({
|
|
|
46
46
|
description: 'Course date of an opened course run block',
|
|
47
47
|
defaultMessage: 'From {startDate} {endDate, select, undefined {} other {to {endDate}}}',
|
|
48
48
|
},
|
|
49
|
+
coursePrice: {
|
|
50
|
+
id: 'components.SyllabusCourseRun.coursePrice',
|
|
51
|
+
description: 'Title of the course enrollment price section of an opened course run block',
|
|
52
|
+
defaultMessage: 'Enrollment price',
|
|
53
|
+
},
|
|
54
|
+
certificationPrice: {
|
|
55
|
+
id: 'components.SyllabusCourseRun.certificationPrice',
|
|
56
|
+
description: 'Title of the certification price section of an opened course run block',
|
|
57
|
+
defaultMessage: 'Certification price',
|
|
58
|
+
},
|
|
59
|
+
coursePaidOffer: {
|
|
60
|
+
id: 'components.SyllabusCourseRun.coursePaidOffer',
|
|
61
|
+
description: 'Message for the paid course offer of an opened course run block',
|
|
62
|
+
defaultMessage: 'The course content is paid.',
|
|
63
|
+
},
|
|
64
|
+
courseFreeOffer: {
|
|
65
|
+
id: 'components.SyllabusCourseRun.courseFreeOffer',
|
|
66
|
+
description: 'Message for the free course offer of an opened course run block',
|
|
67
|
+
defaultMessage: 'The course content is free.',
|
|
68
|
+
},
|
|
69
|
+
coursePartiallyFree: {
|
|
70
|
+
id: 'components.SyllabusCourseRun.coursePartiallyFree',
|
|
71
|
+
description: 'Message for the partially free course offer of an opened course run block',
|
|
72
|
+
defaultMessage: 'The course content is free.',
|
|
73
|
+
},
|
|
74
|
+
courseSubscriptionOffer: {
|
|
75
|
+
id: 'components.SyllabusCourseRun.courseSubscriptionOffer',
|
|
76
|
+
description: 'Message for the subscription course offer of an opened course run block',
|
|
77
|
+
defaultMessage: 'Subscribe to access the course content.',
|
|
78
|
+
},
|
|
79
|
+
certificatePaidOffer: {
|
|
80
|
+
id: 'components.SyllabusCourseRun.certificatePaidOffer',
|
|
81
|
+
description: 'Messagge for the paid certification offer of an opened course run block',
|
|
82
|
+
defaultMessage: 'The certification process is paid.',
|
|
83
|
+
},
|
|
84
|
+
certificateFreeOffer: {
|
|
85
|
+
id: 'components.SyllabusCourseRun.certificateFreeOffer',
|
|
86
|
+
description: 'Message for the free certification offer of an opened course run block',
|
|
87
|
+
defaultMessage: 'The certification process is free.',
|
|
88
|
+
},
|
|
89
|
+
certificateSubscriptionOffer: {
|
|
90
|
+
id: 'components.SyllabusCourseRun.certificateSubscriptionOffer',
|
|
91
|
+
description: 'Message for the subscription certification offer of an opened course run block',
|
|
92
|
+
defaultMessage: 'The certification process is offered through subscription.',
|
|
93
|
+
},
|
|
49
94
|
});
|
|
50
95
|
|
|
51
96
|
const OpenedCourseRun = ({
|
|
@@ -63,6 +108,44 @@ const OpenedCourseRun = ({
|
|
|
63
108
|
const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
|
|
64
109
|
const start = courseRun.start ? formatDate(courseRun.start) : '...';
|
|
65
110
|
const end = courseRun.end ? formatDate(courseRun.end) : '...';
|
|
111
|
+
let courseOfferMessage = null;
|
|
112
|
+
let certificationOfferMessage = null;
|
|
113
|
+
let enrollmentPrice = '';
|
|
114
|
+
let certificatePrice = '';
|
|
115
|
+
|
|
116
|
+
if (courseRun.offer) {
|
|
117
|
+
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
118
|
+
courseOfferMessage = {
|
|
119
|
+
PAID: messages.coursePaidOffer,
|
|
120
|
+
FREE: messages.courseFreeOffer,
|
|
121
|
+
PARTIALLY_FREE: messages.coursePartiallyFree,
|
|
122
|
+
SUBSCRIPTION: messages.courseSubscriptionOffer,
|
|
123
|
+
}[offer];
|
|
124
|
+
|
|
125
|
+
if ((courseRun.price ?? -1) >= 0) {
|
|
126
|
+
enrollmentPrice = intl.formatNumber(courseRun.price!, {
|
|
127
|
+
style: 'currency',
|
|
128
|
+
currency: courseRun.price_currency,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (courseRun.certificate_offer) {
|
|
134
|
+
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
|
|
135
|
+
certificationOfferMessage = {
|
|
136
|
+
PAID: messages.certificatePaidOffer,
|
|
137
|
+
FREE: messages.certificateFreeOffer,
|
|
138
|
+
SUBSCRIPTION: messages.certificateSubscriptionOffer,
|
|
139
|
+
}[certificationOffer];
|
|
140
|
+
|
|
141
|
+
if ((courseRun.certificate_price ?? -1) >= 0) {
|
|
142
|
+
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
|
|
143
|
+
style: 'currency',
|
|
144
|
+
currency: courseRun.price_currency,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
66
149
|
return (
|
|
67
150
|
<>
|
|
68
151
|
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
|
|
@@ -99,6 +182,30 @@ const OpenedCourseRun = ({
|
|
|
99
182
|
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
|
|
100
183
|
</>
|
|
101
184
|
)}
|
|
185
|
+
{courseOfferMessage && (
|
|
186
|
+
<>
|
|
187
|
+
<dt>
|
|
188
|
+
<FormattedMessage {...messages.coursePrice} />
|
|
189
|
+
</dt>
|
|
190
|
+
<dd>
|
|
191
|
+
<FormattedMessage {...courseOfferMessage} />
|
|
192
|
+
<br />
|
|
193
|
+
{enrollmentPrice}
|
|
194
|
+
</dd>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
{certificationOfferMessage && (
|
|
198
|
+
<>
|
|
199
|
+
<dt>
|
|
200
|
+
<FormattedMessage {...messages.certificationPrice} />
|
|
201
|
+
</dt>
|
|
202
|
+
<dd>
|
|
203
|
+
<FormattedMessage {...certificationOfferMessage} />
|
|
204
|
+
<br />
|
|
205
|
+
{certificatePrice}
|
|
206
|
+
</dd>
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
102
209
|
</dl>
|
|
103
210
|
{findLmsBackend(courseRun.resource_link) ? (
|
|
104
211
|
<CourseRunEnrollment courseRun={courseRun} />
|
|
@@ -41,6 +41,51 @@ const messages = defineMessages({
|
|
|
41
41
|
description: 'Self paced course run block with no end date',
|
|
42
42
|
defaultMessage: 'Available',
|
|
43
43
|
},
|
|
44
|
+
coursePrice: {
|
|
45
|
+
id: 'components.SyllabusCourseRunCompacted.coursePrice',
|
|
46
|
+
description: 'Title of the course enrollment price section of an opened course run block',
|
|
47
|
+
defaultMessage: 'Enrollment price',
|
|
48
|
+
},
|
|
49
|
+
certificationPrice: {
|
|
50
|
+
id: 'components.SyllabusCourseRunCompacted.certificationPrice',
|
|
51
|
+
description: 'Title of the certification price section of an opened course run block',
|
|
52
|
+
defaultMessage: 'Certification price',
|
|
53
|
+
},
|
|
54
|
+
coursePaidOffer: {
|
|
55
|
+
id: 'components.SyllabusCourseRunCompacted.coursePaidOffer',
|
|
56
|
+
description: 'Message for the paid course offer of an opened course run block',
|
|
57
|
+
defaultMessage: 'The course content is paid.',
|
|
58
|
+
},
|
|
59
|
+
courseFreeOffer: {
|
|
60
|
+
id: 'components.SyllabusCourseRunCompacted.courseFreeOffer',
|
|
61
|
+
description: 'Message for the free course offer of an opened course run block',
|
|
62
|
+
defaultMessage: 'The course content is free.',
|
|
63
|
+
},
|
|
64
|
+
coursePartiallyFree: {
|
|
65
|
+
id: 'components.SyllabusCourseRunCompacted.coursePartiallyFree',
|
|
66
|
+
description: 'Message for the partially free course offer of an opened course run block',
|
|
67
|
+
defaultMessage: 'The course content is free.',
|
|
68
|
+
},
|
|
69
|
+
courseSubscriptionOffer: {
|
|
70
|
+
id: 'components.SyllabusCourseRunCompacted.courseSubscriptionOffer',
|
|
71
|
+
description: 'Message for the subscription course offer of an opened course run block',
|
|
72
|
+
defaultMessage: 'Subscribe to access the course content.',
|
|
73
|
+
},
|
|
74
|
+
certificatePaidOffer: {
|
|
75
|
+
id: 'components.SyllabusCourseRunCompacted.certificatePaidOffer',
|
|
76
|
+
description: 'Messagge for the paid certification offer of an opened course run block',
|
|
77
|
+
defaultMessage: 'The certification process is paid.',
|
|
78
|
+
},
|
|
79
|
+
certificateFreeOffer: {
|
|
80
|
+
id: 'components.SyllabusCourseRunCompacted.certificateFreeOffer',
|
|
81
|
+
description: 'Message for the free certification offer of an opened course run block',
|
|
82
|
+
defaultMessage: 'The certification process is free.',
|
|
83
|
+
},
|
|
84
|
+
certificateSubscriptionOffer: {
|
|
85
|
+
id: 'components.SyllabusCourseRunCompacted.certificateSubscriptionOffer',
|
|
86
|
+
description: 'Message for the subscription certification offer of an opened course run block',
|
|
87
|
+
defaultMessage: 'The certification process is offered through subscription.',
|
|
88
|
+
},
|
|
44
89
|
});
|
|
45
90
|
|
|
46
91
|
const OpenedSelfPacedCourseRun = ({
|
|
@@ -54,6 +99,44 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
54
99
|
const intl = useIntl();
|
|
55
100
|
const end = courseRun.end ? formatDate(courseRun.end) : '...';
|
|
56
101
|
const hasEndDate = end !== '...';
|
|
102
|
+
let courseOfferMessage = null;
|
|
103
|
+
let certificationOfferMessage = null;
|
|
104
|
+
let enrollmentPrice = '';
|
|
105
|
+
let certificatePrice = '';
|
|
106
|
+
|
|
107
|
+
if (courseRun.offer) {
|
|
108
|
+
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
109
|
+
courseOfferMessage = {
|
|
110
|
+
PAID: messages.coursePaidOffer,
|
|
111
|
+
FREE: messages.courseFreeOffer,
|
|
112
|
+
PARTIALLY_FREE: messages.coursePartiallyFree,
|
|
113
|
+
SUBSCRIPTION: messages.courseSubscriptionOffer,
|
|
114
|
+
}[offer];
|
|
115
|
+
|
|
116
|
+
if ((courseRun.price ?? -1) >= 0) {
|
|
117
|
+
enrollmentPrice = intl.formatNumber(courseRun.price!, {
|
|
118
|
+
style: 'currency',
|
|
119
|
+
currency: courseRun.price_currency,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (courseRun.certificate_offer) {
|
|
125
|
+
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
|
|
126
|
+
certificationOfferMessage = {
|
|
127
|
+
PAID: messages.certificatePaidOffer,
|
|
128
|
+
FREE: messages.certificateFreeOffer,
|
|
129
|
+
SUBSCRIPTION: messages.certificateSubscriptionOffer,
|
|
130
|
+
}[certificationOffer];
|
|
131
|
+
|
|
132
|
+
if ((courseRun.certificate_price ?? -1) >= 0) {
|
|
133
|
+
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
|
|
134
|
+
style: 'currency',
|
|
135
|
+
currency: courseRun.price_currency,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
57
140
|
return (
|
|
58
141
|
<>
|
|
59
142
|
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
|
|
@@ -83,6 +166,30 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
83
166
|
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
|
|
84
167
|
</>
|
|
85
168
|
)}
|
|
169
|
+
{courseOfferMessage && (
|
|
170
|
+
<>
|
|
171
|
+
<dt>
|
|
172
|
+
<FormattedMessage {...messages.coursePrice} />
|
|
173
|
+
</dt>
|
|
174
|
+
<dd>
|
|
175
|
+
<FormattedMessage {...courseOfferMessage} />
|
|
176
|
+
<br />
|
|
177
|
+
{enrollmentPrice}
|
|
178
|
+
</dd>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
{certificationOfferMessage && (
|
|
182
|
+
<>
|
|
183
|
+
<dt>
|
|
184
|
+
<FormattedMessage {...messages.certificationPrice} />
|
|
185
|
+
</dt>
|
|
186
|
+
<dd>
|
|
187
|
+
<FormattedMessage {...certificationOfferMessage} />
|
|
188
|
+
<br />
|
|
189
|
+
{certificatePrice}
|
|
190
|
+
</dd>
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
86
193
|
</dl>
|
|
87
194
|
{findLmsBackend(courseRun.resource_link) ? (
|
|
88
195
|
<CourseRunEnrollment courseRun={courseRun} />
|
|
@@ -30,6 +30,8 @@ import { computeStates } from 'utils/CourseRuns';
|
|
|
30
30
|
import { IntlHelper } from 'utils/IntlHelper';
|
|
31
31
|
import { render } from 'utils/test/render';
|
|
32
32
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
33
|
+
import { SyllabusCourseRunCompacted } from './components/SyllabusCourseRunCompacted';
|
|
34
|
+
import { SyllabusCourseRun } from './components/SyllabusCourseRun';
|
|
33
35
|
|
|
34
36
|
jest.mock('utils/context', () => {
|
|
35
37
|
const mock = mockRichieContextFactory().one();
|
|
@@ -127,7 +129,6 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
127
129
|
const languagesContainer = languagesNode.nextSibling! as HTMLElement;
|
|
128
130
|
getByText(languagesContainer, IntlHelper.getLocalizedLanguages(courseRun.languages, intl));
|
|
129
131
|
|
|
130
|
-
expect(languagesContainer.nextSibling).toBeNull();
|
|
131
132
|
getByRole(runContainer, 'link', {
|
|
132
133
|
name: StringHelper.capitalizeFirst(courseRun.state.call_to_action)!,
|
|
133
134
|
});
|
|
@@ -568,7 +569,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
568
569
|
|
|
569
570
|
const portalContainer = getPortalContainer();
|
|
570
571
|
|
|
571
|
-
// Expect that
|
|
572
|
+
// Expect that 'is-hidden' class is set only to course runs in (MAX_ARCHIVED_COURSE_RUNS)nth-plus position.
|
|
572
573
|
expect(portalContainer.querySelectorAll('li').length).toBe(MAX_ARCHIVED_COURSE_RUNS * 2);
|
|
573
574
|
portalContainer.querySelectorAll('li').forEach((listElement, i) => {
|
|
574
575
|
expectCourseRunInList(listElement, courseRuns[i]);
|
|
@@ -585,7 +586,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
585
586
|
// click on view more.
|
|
586
587
|
await act(async () => user.click(button));
|
|
587
588
|
|
|
588
|
-
// expect that
|
|
589
|
+
// expect that 'is-hidden' are removed.
|
|
589
590
|
portalContainer.querySelectorAll('li').forEach((listElement, i) => {
|
|
590
591
|
expectCourseRunInList(listElement, courseRuns[i]);
|
|
591
592
|
expect(listElement.classList).not.toContain('is-hidden');
|
|
@@ -615,7 +616,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
615
616
|
const portalContainer = getPortalContainer();
|
|
616
617
|
expect(screen.queryByRole('button', { name: 'View more' })).not.toBeInTheDocument();
|
|
617
618
|
|
|
618
|
-
// expect that
|
|
619
|
+
// expect that 'is-hidden' is not set.
|
|
619
620
|
expect(portalContainer.querySelectorAll('li').length).toBe(MAX_ARCHIVED_COURSE_RUNS - 1);
|
|
620
621
|
portalContainer.querySelectorAll('li').forEach((listElement, i) => {
|
|
621
622
|
expectCourseRunInList(listElement, courseRuns[i]);
|
|
@@ -1009,7 +1010,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
1009
1010
|
const listElements = screen.getAllByRole('listitem');
|
|
1010
1011
|
expect(listElements.length).toBe(4);
|
|
1011
1012
|
|
|
1012
|
-
// Assert there is only one link, one label
|
|
1013
|
+
// Assert there is only one link, one label 'Enrolled' and one request to retrieve enrollment.
|
|
1013
1014
|
expect(await screen.findAllByText('Enrolled')).toHaveLength(1);
|
|
1014
1015
|
|
|
1015
1016
|
const links = screen.getAllByRole('link');
|
|
@@ -1024,4 +1025,448 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
1024
1025
|
`https://demo.endpoint/api/enrollment/v1/enrollment/${user.username},${onGoingCourseRun.resource_link}`,
|
|
1025
1026
|
);
|
|
1026
1027
|
});
|
|
1028
|
+
|
|
1029
|
+
it('renders price information as paid and paid on SyllabusCourseRunCompacted', async () => {
|
|
1030
|
+
const course = PacedCourseFactory().one();
|
|
1031
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1032
|
+
languages: ['en'],
|
|
1033
|
+
offer: 'paid',
|
|
1034
|
+
certificate_offer: 'paid',
|
|
1035
|
+
price_currency: 'EUR',
|
|
1036
|
+
price: 49.99,
|
|
1037
|
+
certificate_price: 59.99,
|
|
1038
|
+
}).one();
|
|
1039
|
+
|
|
1040
|
+
render(
|
|
1041
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1042
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1043
|
+
</div>,
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
const content = getHeaderContainer().innerHTML;
|
|
1047
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1048
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it('renders price information as subscription on SyllabusCourseRunCompacted', async () => {
|
|
1052
|
+
const course = PacedCourseFactory().one();
|
|
1053
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1054
|
+
languages: ['en'],
|
|
1055
|
+
offer: 'Subscription',
|
|
1056
|
+
certificate_offer: 'Subscription',
|
|
1057
|
+
price_currency: 'EUR',
|
|
1058
|
+
price: 49.99,
|
|
1059
|
+
certificate_price: 59.99,
|
|
1060
|
+
}).one();
|
|
1061
|
+
|
|
1062
|
+
render(
|
|
1063
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1064
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1065
|
+
</div>,
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
const content = getHeaderContainer().innerHTML;
|
|
1069
|
+
expect(content).toContain('<dd>Subscribe to access the course content.<br>€49.99</dd>');
|
|
1070
|
+
expect(content).toContain(
|
|
1071
|
+
'<dd>The certification process is offered through subscription.<br>€59.99</dd>',
|
|
1072
|
+
);
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it('renders price information as Partially free on SyllabusCourseRunCompacted', async () => {
|
|
1076
|
+
const course = PacedCourseFactory().one();
|
|
1077
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1078
|
+
languages: ['en'],
|
|
1079
|
+
offer: 'Partially free',
|
|
1080
|
+
certificate_offer: 'paid',
|
|
1081
|
+
price_currency: 'EUR',
|
|
1082
|
+
price: 0,
|
|
1083
|
+
certificate_price: 59.99,
|
|
1084
|
+
}).one();
|
|
1085
|
+
|
|
1086
|
+
render(
|
|
1087
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1088
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1089
|
+
</div>,
|
|
1090
|
+
);
|
|
1091
|
+
|
|
1092
|
+
const content = getHeaderContainer().innerHTML;
|
|
1093
|
+
expect(content).toContain('<dd>The course content is free.<br>€0.00</dd>');
|
|
1094
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it('renders price information as paid and free on SyllabusCourseRunCompacted', async () => {
|
|
1098
|
+
const course = PacedCourseFactory().one();
|
|
1099
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1100
|
+
languages: ['en'],
|
|
1101
|
+
offer: 'paid',
|
|
1102
|
+
certificate_offer: 'free',
|
|
1103
|
+
price_currency: 'EUR',
|
|
1104
|
+
price: 49.99,
|
|
1105
|
+
certificate_price: 0,
|
|
1106
|
+
}).one();
|
|
1107
|
+
|
|
1108
|
+
render(
|
|
1109
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1110
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1111
|
+
</div>,
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
const content = getHeaderContainer().innerHTML;
|
|
1115
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1116
|
+
expect(content).toContain('<dd>The certification process is free.<br>€0.00</dd>');
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('does not render price information on SyllabusCourseRunCompacted', async () => {
|
|
1120
|
+
const course = PacedCourseFactory().one();
|
|
1121
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1122
|
+
languages: ['en'],
|
|
1123
|
+
offer: undefined,
|
|
1124
|
+
certificate_offer: undefined,
|
|
1125
|
+
price: 59.99,
|
|
1126
|
+
certificate_price: 59.99,
|
|
1127
|
+
}).one();
|
|
1128
|
+
|
|
1129
|
+
render(
|
|
1130
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1131
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1132
|
+
</div>,
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
const content = getHeaderContainer().innerHTML;
|
|
1136
|
+
expect(content).not.toContain('The course content is paid');
|
|
1137
|
+
expect(content).not.toContain('The certification process is paid.');
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
it('does not render course price information on SyllabusCourseRunCompacted', async () => {
|
|
1141
|
+
const course = PacedCourseFactory().one();
|
|
1142
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1143
|
+
languages: ['en'],
|
|
1144
|
+
certificate_offer: 'paid',
|
|
1145
|
+
price_currency: 'EUR',
|
|
1146
|
+
offer: undefined,
|
|
1147
|
+
price: 59.99,
|
|
1148
|
+
certificate_price: 59.99,
|
|
1149
|
+
}).one();
|
|
1150
|
+
|
|
1151
|
+
render(
|
|
1152
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1153
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1154
|
+
</div>,
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
const content = getHeaderContainer().innerHTML;
|
|
1158
|
+
expect(content).not.toContain('The course content is paid.');
|
|
1159
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
it('does not render certificate price information on SyllabusCourseRunCompacted', async () => {
|
|
1163
|
+
const course = PacedCourseFactory().one();
|
|
1164
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1165
|
+
languages: ['en'],
|
|
1166
|
+
price_currency: 'EUR',
|
|
1167
|
+
offer: 'paid',
|
|
1168
|
+
price: 49.99,
|
|
1169
|
+
certificate_offer: undefined,
|
|
1170
|
+
certificate_price: undefined,
|
|
1171
|
+
}).one();
|
|
1172
|
+
|
|
1173
|
+
render(
|
|
1174
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1175
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1176
|
+
</div>,
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
const content = getHeaderContainer().innerHTML;
|
|
1180
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1181
|
+
expect(content).not.toContain('The certification process is paid.');
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('does not render prices but only offers on SyllabusCourseRunCompacted', async () => {
|
|
1185
|
+
const course = PacedCourseFactory().one();
|
|
1186
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1187
|
+
languages: ['en'],
|
|
1188
|
+
offer: 'free',
|
|
1189
|
+
certificate_offer: 'free',
|
|
1190
|
+
price_currency: 'EUR',
|
|
1191
|
+
price: undefined,
|
|
1192
|
+
certificate_price: undefined,
|
|
1193
|
+
}).one();
|
|
1194
|
+
|
|
1195
|
+
render(
|
|
1196
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1197
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1198
|
+
</div>,
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
const content = getHeaderContainer().innerHTML;
|
|
1202
|
+
expect(content).toContain('<dd>The course content is free.<br></dd>');
|
|
1203
|
+
expect(content).toContain('<dd>The certification process is free.<br></dd>');
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
it('renders prices as zero on SyllabusCourseRunCompacted', async () => {
|
|
1207
|
+
const course = PacedCourseFactory().one();
|
|
1208
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1209
|
+
languages: ['en'],
|
|
1210
|
+
offer: 'free',
|
|
1211
|
+
certificate_offer: 'free',
|
|
1212
|
+
price_currency: 'EUR',
|
|
1213
|
+
price: 0,
|
|
1214
|
+
certificate_price: 0,
|
|
1215
|
+
}).one();
|
|
1216
|
+
|
|
1217
|
+
render(
|
|
1218
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1219
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1220
|
+
</div>,
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
const content = getHeaderContainer().innerHTML;
|
|
1224
|
+
expect(content).toContain('<dd>The course content is free.<br>€0.00</dd>');
|
|
1225
|
+
expect(content).toContain('<dd>The certification process is free.<br>€0.00</dd>');
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
it('does not render invalid offers on SyllabusCourseRunCompacted', async () => {
|
|
1229
|
+
const course = PacedCourseFactory().one();
|
|
1230
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1231
|
+
languages: ['en'],
|
|
1232
|
+
offer: 'invalid',
|
|
1233
|
+
certificate_offer: 'invalid',
|
|
1234
|
+
price_currency: 'EUR',
|
|
1235
|
+
price: 59.99,
|
|
1236
|
+
certificate_price: 59.99,
|
|
1237
|
+
}).one();
|
|
1238
|
+
|
|
1239
|
+
render(
|
|
1240
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1241
|
+
<SyllabusCourseRunCompacted courseRun={courseRun} course={course} showLanguages={false} />
|
|
1242
|
+
</div>,
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
const content = getHeaderContainer().innerHTML;
|
|
1246
|
+
expect(content).not.toContain('The course content is');
|
|
1247
|
+
expect(content).not.toContain('The certification process is');
|
|
1248
|
+
expect(content).not.toContain('<br>€59.99');
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
it('renders price information as paid and paid on SyllabusCourseRun', async () => {
|
|
1252
|
+
const course = PacedCourseFactory().one();
|
|
1253
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1254
|
+
languages: ['en'],
|
|
1255
|
+
offer: 'paid',
|
|
1256
|
+
certificate_offer: 'paid',
|
|
1257
|
+
price_currency: 'EUR',
|
|
1258
|
+
price: 49.99,
|
|
1259
|
+
certificate_price: 59.99,
|
|
1260
|
+
}).one();
|
|
1261
|
+
|
|
1262
|
+
render(
|
|
1263
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1264
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1265
|
+
</div>,
|
|
1266
|
+
);
|
|
1267
|
+
|
|
1268
|
+
const content = getHeaderContainer().innerHTML;
|
|
1269
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1270
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
it('renders price information as subscription on SyllabusCourseRun', async () => {
|
|
1274
|
+
const course = PacedCourseFactory().one();
|
|
1275
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1276
|
+
languages: ['en'],
|
|
1277
|
+
offer: 'Subscription',
|
|
1278
|
+
certificate_offer: 'Subscription',
|
|
1279
|
+
price_currency: 'EUR',
|
|
1280
|
+
price: 49.99,
|
|
1281
|
+
certificate_price: 59.99,
|
|
1282
|
+
}).one();
|
|
1283
|
+
|
|
1284
|
+
render(
|
|
1285
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1286
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1287
|
+
</div>,
|
|
1288
|
+
);
|
|
1289
|
+
|
|
1290
|
+
const content = getHeaderContainer().innerHTML;
|
|
1291
|
+
expect(content).toContain('<dd>Subscribe to access the course content.<br>€49.99</dd>');
|
|
1292
|
+
expect(content).toContain(
|
|
1293
|
+
'<dd>The certification process is offered through subscription.<br>€59.99</dd>',
|
|
1294
|
+
);
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
it('renders price information as Partially free on SyllabusCourseRun', async () => {
|
|
1298
|
+
const course = PacedCourseFactory().one();
|
|
1299
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1300
|
+
languages: ['en'],
|
|
1301
|
+
offer: 'Partially free',
|
|
1302
|
+
certificate_offer: 'paid',
|
|
1303
|
+
price_currency: 'EUR',
|
|
1304
|
+
price: 0,
|
|
1305
|
+
certificate_price: 59.99,
|
|
1306
|
+
}).one();
|
|
1307
|
+
|
|
1308
|
+
render(
|
|
1309
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1310
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1311
|
+
</div>,
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
const content = getHeaderContainer().innerHTML;
|
|
1315
|
+
expect(content).toContain('<dd>The course content is free.<br>€0.00</dd>');
|
|
1316
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
it('renders price information as paid and free on SyllabusCourseRun', async () => {
|
|
1320
|
+
const course = PacedCourseFactory().one();
|
|
1321
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1322
|
+
languages: ['en'],
|
|
1323
|
+
offer: 'paid',
|
|
1324
|
+
certificate_offer: 'free',
|
|
1325
|
+
price_currency: 'EUR',
|
|
1326
|
+
price: 49.99,
|
|
1327
|
+
certificate_price: 0,
|
|
1328
|
+
}).one();
|
|
1329
|
+
|
|
1330
|
+
render(
|
|
1331
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1332
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1333
|
+
</div>,
|
|
1334
|
+
);
|
|
1335
|
+
|
|
1336
|
+
const content = getHeaderContainer().innerHTML;
|
|
1337
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1338
|
+
expect(content).toContain('<dd>The certification process is free.<br>€0.00</dd>');
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it('does not render price information on SyllabusCourseRun', async () => {
|
|
1342
|
+
const course = PacedCourseFactory().one();
|
|
1343
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1344
|
+
languages: ['en'],
|
|
1345
|
+
offer: undefined,
|
|
1346
|
+
certificate_offer: undefined,
|
|
1347
|
+
price: 59.99,
|
|
1348
|
+
certificate_price: 59.99,
|
|
1349
|
+
}).one();
|
|
1350
|
+
|
|
1351
|
+
render(
|
|
1352
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1353
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1354
|
+
</div>,
|
|
1355
|
+
);
|
|
1356
|
+
|
|
1357
|
+
const content = getHeaderContainer().innerHTML;
|
|
1358
|
+
expect(content).not.toContain('The course content is paid');
|
|
1359
|
+
expect(content).not.toContain('The certification process is paid.');
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('does not render course price information on SyllabusCourseRun', async () => {
|
|
1363
|
+
const course = PacedCourseFactory().one();
|
|
1364
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1365
|
+
languages: ['en'],
|
|
1366
|
+
offer: undefined,
|
|
1367
|
+
price: 59.99,
|
|
1368
|
+
price_currency: 'EUR',
|
|
1369
|
+
certificate_offer: 'paid',
|
|
1370
|
+
certificate_price: 59.99,
|
|
1371
|
+
}).one();
|
|
1372
|
+
|
|
1373
|
+
render(
|
|
1374
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1375
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1376
|
+
</div>,
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
const content = getHeaderContainer().innerHTML;
|
|
1380
|
+
expect(content).not.toContain('The course content is paid.');
|
|
1381
|
+
expect(content).toContain('<dd>The certification process is paid.<br>€59.99</dd>');
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
it('does not render certificate price information on SyllabusCourseRun', async () => {
|
|
1385
|
+
const course = PacedCourseFactory().one();
|
|
1386
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1387
|
+
languages: ['en'],
|
|
1388
|
+
price_currency: 'EUR',
|
|
1389
|
+
offer: 'paid',
|
|
1390
|
+
price: 49.99,
|
|
1391
|
+
certificate_offer: undefined,
|
|
1392
|
+
certificate_price: undefined,
|
|
1393
|
+
}).one();
|
|
1394
|
+
|
|
1395
|
+
render(
|
|
1396
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1397
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1398
|
+
</div>,
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
const content = getHeaderContainer().innerHTML;
|
|
1402
|
+
expect(content).toContain('<dd>The course content is paid.<br>€49.99</dd>');
|
|
1403
|
+
expect(content).not.toContain('The certification process is paid.');
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
it('does not render prices but only offers on SyllabusCourseRun', async () => {
|
|
1407
|
+
const course = PacedCourseFactory().one();
|
|
1408
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1409
|
+
languages: ['en'],
|
|
1410
|
+
offer: 'free',
|
|
1411
|
+
certificate_offer: 'free',
|
|
1412
|
+
price_currency: 'EUR',
|
|
1413
|
+
price: undefined,
|
|
1414
|
+
certificate_price: undefined,
|
|
1415
|
+
}).one();
|
|
1416
|
+
|
|
1417
|
+
render(
|
|
1418
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1419
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1420
|
+
</div>,
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1423
|
+
const content = getHeaderContainer().innerHTML;
|
|
1424
|
+
expect(content).toContain('<dd>The course content is free.<br></dd>');
|
|
1425
|
+
expect(content).toContain('<dd>The certification process is free.<br></dd>');
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
it('renders prices as zero on SyllabusCourseRun', async () => {
|
|
1429
|
+
const course = PacedCourseFactory().one();
|
|
1430
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1431
|
+
languages: ['en'],
|
|
1432
|
+
offer: 'free',
|
|
1433
|
+
certificate_offer: 'free',
|
|
1434
|
+
price_currency: 'EUR',
|
|
1435
|
+
price: 0,
|
|
1436
|
+
certificate_price: 0,
|
|
1437
|
+
}).one();
|
|
1438
|
+
|
|
1439
|
+
render(
|
|
1440
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1441
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1442
|
+
</div>,
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
const content = getHeaderContainer().innerHTML;
|
|
1446
|
+
expect(content).toContain('<dd>The course content is free.<br>€0.00</dd>');
|
|
1447
|
+
expect(content).toContain('<dd>The certification process is free.<br>€0.00</dd>');
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
it('does not render invalid offers on SyllabusCourseRun', async () => {
|
|
1451
|
+
const course = PacedCourseFactory().one();
|
|
1452
|
+
const courseRun: CourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
1453
|
+
languages: ['en'],
|
|
1454
|
+
offer: 'invalid',
|
|
1455
|
+
certificate_offer: 'invalid',
|
|
1456
|
+
price_currency: 'EUR',
|
|
1457
|
+
price: 59.99,
|
|
1458
|
+
certificate_price: 59.99,
|
|
1459
|
+
}).one();
|
|
1460
|
+
|
|
1461
|
+
render(
|
|
1462
|
+
<div className="course-detail__row course-detail__runs course-detail__runs--open">
|
|
1463
|
+
<SyllabusCourseRun courseRun={courseRun} course={course} showLanguages={false} />
|
|
1464
|
+
</div>,
|
|
1465
|
+
);
|
|
1466
|
+
|
|
1467
|
+
const content = getHeaderContainer().innerHTML;
|
|
1468
|
+
expect(content).not.toContain('The course content is');
|
|
1469
|
+
expect(content).not.toContain('The certification process is');
|
|
1470
|
+
expect(content).not.toContain('<br>€59.99');
|
|
1471
|
+
});
|
|
1027
1472
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "2.34.1-
|
|
3
|
+
"version": "2.34.1-dev21",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -38,75 +38,75 @@
|
|
|
38
38
|
"not dead"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@babel/core": "7.26.
|
|
41
|
+
"@babel/core": "7.26.9",
|
|
42
42
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
|
43
43
|
"@babel/plugin-transform-modules-commonjs": "7.26.3",
|
|
44
|
-
"@babel/preset-env": "7.26.
|
|
44
|
+
"@babel/preset-env": "7.26.9",
|
|
45
45
|
"@babel/preset-react": "7.26.3",
|
|
46
46
|
"@babel/preset-typescript": "7.26.0",
|
|
47
|
-
"@faker-js/faker": "9.
|
|
48
|
-
"@formatjs/cli": "6.
|
|
49
|
-
"@formatjs/intl-relativetimeformat": "11.4.
|
|
50
|
-
"@hookform/resolvers": "
|
|
47
|
+
"@faker-js/faker": "9.6.0",
|
|
48
|
+
"@formatjs/cli": "6.6.1",
|
|
49
|
+
"@formatjs/intl-relativetimeformat": "11.4.10",
|
|
50
|
+
"@hookform/resolvers": "4.1.3",
|
|
51
51
|
"@lyracom/embedded-form-glue": "1.4.2",
|
|
52
52
|
"@openfun/cunningham-react": "3.0.0",
|
|
53
53
|
"@openfun/cunningham-tokens": "2.2.0",
|
|
54
|
-
"@sentry/browser": "
|
|
55
|
-
"@sentry/types": "
|
|
56
|
-
"@storybook/addon-actions": "8.4
|
|
57
|
-
"@storybook/addon-essentials": "8.4
|
|
58
|
-
"@storybook/addon-interactions": "8.4
|
|
59
|
-
"@storybook/addon-links": "8.4
|
|
60
|
-
"@storybook/react": "8.4
|
|
61
|
-
"@storybook/react-webpack5": "8.4
|
|
62
|
-
"@storybook/test": "8.4
|
|
63
|
-
"@tanstack/query-core": "5.
|
|
64
|
-
"@tanstack/query-sync-storage-persister": "5.
|
|
65
|
-
"@tanstack/react-query": "5.
|
|
66
|
-
"@tanstack/react-query-devtools": "5.
|
|
67
|
-
"@tanstack/react-query-persist-client": "5.
|
|
54
|
+
"@sentry/browser": "9.5.0",
|
|
55
|
+
"@sentry/types": "9.5.0",
|
|
56
|
+
"@storybook/addon-actions": "8.6.4",
|
|
57
|
+
"@storybook/addon-essentials": "8.6.4",
|
|
58
|
+
"@storybook/addon-interactions": "8.6.4",
|
|
59
|
+
"@storybook/addon-links": "8.6.4",
|
|
60
|
+
"@storybook/react": "8.6.4",
|
|
61
|
+
"@storybook/react-webpack5": "8.6.4",
|
|
62
|
+
"@storybook/test": "8.6.4",
|
|
63
|
+
"@tanstack/query-core": "5.67.2",
|
|
64
|
+
"@tanstack/query-sync-storage-persister": "5.67.2",
|
|
65
|
+
"@tanstack/react-query": "5.67.2",
|
|
66
|
+
"@tanstack/react-query-devtools": "5.67.2",
|
|
67
|
+
"@tanstack/react-query-persist-client": "5.67.2",
|
|
68
68
|
"@testing-library/dom": "10.4.0",
|
|
69
69
|
"@testing-library/jest-dom": "6.6.3",
|
|
70
|
-
"@testing-library/react": "16.
|
|
71
|
-
"@testing-library/user-event": "14.
|
|
72
|
-
"@types/fetch-mock": "
|
|
73
|
-
"@types/iframe-resizer": "
|
|
70
|
+
"@testing-library/react": "16.2.0",
|
|
71
|
+
"@testing-library/user-event": "14.6.1",
|
|
72
|
+
"@types/fetch-mock": "9.2.2",
|
|
73
|
+
"@types/iframe-resizer": "4.0.0",
|
|
74
74
|
"@types/jest": "29.5.14",
|
|
75
75
|
"@types/js-cookie": "3.0.6",
|
|
76
76
|
"@types/lodash-es": "4.17.12",
|
|
77
77
|
"@types/node-fetch": "2.6.12",
|
|
78
78
|
"@types/query-string": "6.3.0",
|
|
79
|
-
"@types/react": "19.0.
|
|
79
|
+
"@types/react": "19.0.10",
|
|
80
80
|
"@types/react-autosuggest": "10.1.11",
|
|
81
|
-
"@types/react-dom": "19.0.
|
|
81
|
+
"@types/react-dom": "19.0.4",
|
|
82
82
|
"@types/react-modal": "3.16.3",
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
84
|
-
"@typescript-eslint/parser": "8.
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "8.26.0",
|
|
84
|
+
"@typescript-eslint/parser": "8.26.0",
|
|
85
85
|
"babel-jest": "29.7.0",
|
|
86
|
-
"babel-loader": "
|
|
86
|
+
"babel-loader": "10.0.0",
|
|
87
87
|
"babel-plugin-react-intl": "8.2.25",
|
|
88
88
|
"bootstrap": ">=4.6.0 <5",
|
|
89
89
|
"classnames": "2.5.1",
|
|
90
90
|
"cljs-merge": "1.1.1",
|
|
91
|
-
"core-js": "3.
|
|
91
|
+
"core-js": "3.41.0",
|
|
92
92
|
"downshift": "9.0.8",
|
|
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
|
-
"eslint-config-prettier": "
|
|
97
|
-
"eslint-import-resolver-webpack": "0.13.
|
|
98
|
-
"eslint-plugin-compat": "6.0.
|
|
99
|
-
"eslint-plugin-formatjs": "5.2.
|
|
96
|
+
"eslint-config-prettier": "10.1.1",
|
|
97
|
+
"eslint-import-resolver-webpack": "0.13.10",
|
|
98
|
+
"eslint-plugin-compat": "6.0.2",
|
|
99
|
+
"eslint-plugin-formatjs": "5.2.14",
|
|
100
100
|
"eslint-plugin-import": "2.31.0",
|
|
101
101
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
102
|
-
"eslint-plugin-prettier": "5.2.
|
|
103
|
-
"eslint-plugin-react": "7.37.
|
|
104
|
-
"eslint-plugin-react-hooks": "5.
|
|
105
|
-
"eslint-plugin-storybook": "0.11.
|
|
102
|
+
"eslint-plugin-prettier": "5.2.3",
|
|
103
|
+
"eslint-plugin-react": "7.37.4",
|
|
104
|
+
"eslint-plugin-react-hooks": "5.2.0",
|
|
105
|
+
"eslint-plugin-storybook": "0.11.4",
|
|
106
106
|
"fetch-mock": "<10",
|
|
107
107
|
"file-loader": "6.2.0",
|
|
108
|
-
"glob": "11.0.
|
|
109
|
-
"i18n-iso-countries": "7.
|
|
108
|
+
"glob": "11.0.1",
|
|
109
|
+
"i18n-iso-countries": "7.14.0",
|
|
110
110
|
"iframe-resizer": "<5",
|
|
111
111
|
"intl-pluralrules": "2.0.1",
|
|
112
112
|
"jest": "29.7.0",
|
|
@@ -114,35 +114,35 @@
|
|
|
114
114
|
"js-cookie": "3.0.5",
|
|
115
115
|
"lodash-es": "4.17.21",
|
|
116
116
|
"mdn-polyfills": "5.20.0",
|
|
117
|
-
"msw": "2.
|
|
117
|
+
"msw": "2.7.3",
|
|
118
118
|
"node-fetch": ">2.6.6 <3",
|
|
119
|
-
"nodemon": "3.1.
|
|
120
|
-
"prettier": "3.
|
|
119
|
+
"nodemon": "3.1.9",
|
|
120
|
+
"prettier": "3.5.3",
|
|
121
121
|
"query-string": "9.1.1",
|
|
122
122
|
"react": "19.0.0",
|
|
123
123
|
"react-autosuggest": "10.1.0",
|
|
124
124
|
"react-dom": "19.0.0",
|
|
125
|
-
"react-hook-form": "7.54.
|
|
126
|
-
"react-intl": "7.
|
|
127
|
-
"react-modal": "3.16.
|
|
128
|
-
"react-router": "7.0
|
|
129
|
-
"sass": "1.
|
|
125
|
+
"react-hook-form": "7.54.2",
|
|
126
|
+
"react-intl": "7.1.6",
|
|
127
|
+
"react-modal": "3.16.3",
|
|
128
|
+
"react-router": "7.3.0",
|
|
129
|
+
"sass": "1.85.1",
|
|
130
130
|
"source-map-loader": "5.0.0",
|
|
131
|
-
"storybook": "8.4
|
|
131
|
+
"storybook": "8.6.4",
|
|
132
132
|
"tsconfig-paths-webpack-plugin": "4.2.0",
|
|
133
|
-
"typescript": "5.
|
|
134
|
-
"uuid": "11.0
|
|
135
|
-
"webpack": "5.
|
|
136
|
-
"webpack-cli": "
|
|
133
|
+
"typescript": "5.8.2",
|
|
134
|
+
"uuid": "11.1.0",
|
|
135
|
+
"webpack": "5.98.0",
|
|
136
|
+
"webpack-cli": "6.0.1",
|
|
137
137
|
"whatwg-fetch": "3.6.20",
|
|
138
138
|
"xhr-mock": "2.5.1",
|
|
139
139
|
"yargs": "17.7.2",
|
|
140
|
-
"yup": "1.
|
|
140
|
+
"yup": "1.6.1"
|
|
141
141
|
},
|
|
142
142
|
"resolutions": {
|
|
143
143
|
"@testing-library/dom": "10.4.0",
|
|
144
|
-
"@types/react": "19.0.
|
|
145
|
-
"@types/react-dom": "19.0.
|
|
144
|
+
"@types/react": "19.0.10",
|
|
145
|
+
"@types/react-dom": "19.0.4",
|
|
146
146
|
"react": "19.0.0",
|
|
147
147
|
"react-dom": "19.0.0"
|
|
148
148
|
},
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"yarn": "1.22.22"
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
|
-
"@storybook/addon-mdx-gfm": "8.4
|
|
158
|
-
"@storybook/addon-webpack5-compiler-babel": "3.0.
|
|
157
|
+
"@storybook/addon-mdx-gfm": "8.6.4",
|
|
158
|
+
"@storybook/addon-webpack5-compiler-babel": "3.0.5"
|
|
159
159
|
}
|
|
160
160
|
}
|
package/scss/colors/_theme.scss
CHANGED
|
@@ -47,6 +47,14 @@ $r-theme: (
|
|
|
47
47
|
item-cta-hollow-background: transparent,
|
|
48
48
|
item-cta-hollow-border: transparent,
|
|
49
49
|
item-divider-border: r-color('light-grey'),
|
|
50
|
+
dropdown-border-radius: 8px,
|
|
51
|
+
dropdown-padding: 0.5rem,
|
|
52
|
+
dropdown-gap: 0.5rem,
|
|
53
|
+
dropdown-background-color: r-color('azure2'),
|
|
54
|
+
dropdown-item-background-color: r-color('indianred3'),
|
|
55
|
+
dropdown-item-text-color: r-color('white'),
|
|
56
|
+
dropdown-item-border-radius: 4px,
|
|
57
|
+
dropdown-item-padding: 0.5rem 1rem,
|
|
50
58
|
),
|
|
51
59
|
body-content: (
|
|
52
60
|
base-color: r-color('black'),
|
|
@@ -168,6 +168,10 @@
|
|
|
168
168
|
flex-wrap: nowrap;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
& > .topbar__list {
|
|
172
|
+
position: relative;
|
|
173
|
+
}
|
|
174
|
+
|
|
171
175
|
// Aside menu variation
|
|
172
176
|
&--aside {
|
|
173
177
|
@include sv-flex(1, 0, auto);
|
|
@@ -197,6 +201,76 @@
|
|
|
197
201
|
// Menu item element
|
|
198
202
|
&__item {
|
|
199
203
|
$item-selector: &;
|
|
204
|
+
&.dropdown {
|
|
205
|
+
display: block;
|
|
206
|
+
position: static;
|
|
207
|
+
|
|
208
|
+
& > button {
|
|
209
|
+
width: 100%;
|
|
210
|
+
|
|
211
|
+
& > .icon {
|
|
212
|
+
height: 1rem;
|
|
213
|
+
margin-left: 0.5rem;
|
|
214
|
+
width: 1rem;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
& ul[role='menu'] {
|
|
219
|
+
padding-left: 0.5rem;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@include media-breakpoint-up($r-topbar-breakpoint) {
|
|
224
|
+
&.dropdown {
|
|
225
|
+
// See header_menu.html for the definition of the variables
|
|
226
|
+
--active-background-color: #{r-theme-val(topbar, dropdown-item-background-color)};
|
|
227
|
+
--active-text-color: #{r-theme-val(topbar, dropdown-item-text-color)};
|
|
228
|
+
|
|
229
|
+
& > button[aria-expanded='true'] {
|
|
230
|
+
background-color: r-theme-val(topbar, dropdown-background-color);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
& > button[aria-expanded='true'] + .topbar__sublist {
|
|
234
|
+
display: block;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
& > .topbar__sublist {
|
|
238
|
+
background: r-theme-val(topbar, dropdown-background-color);
|
|
239
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
240
|
+
border-radius: r-theme-val(topbar, dropdown-border-radius);
|
|
241
|
+
display: none;
|
|
242
|
+
position: absolute;
|
|
243
|
+
transform: translateX(-8px);
|
|
244
|
+
max-width: 100%;
|
|
245
|
+
|
|
246
|
+
&--full-width {
|
|
247
|
+
left: 0;
|
|
248
|
+
transform: inherit;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
& > ul[role='menu'] {
|
|
252
|
+
display: flex;
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
gap: r-theme-val(topbar, dropdown-gap);
|
|
255
|
+
padding: r-theme-val(topbar, dropdown-padding);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
& .topbar__item > a {
|
|
259
|
+
background-color: #fff;
|
|
260
|
+
border-radius: r-theme-val(topbar, dropdown-item-border-radius);
|
|
261
|
+
padding: r-theme-val(topbar, dropdown-item-padding);
|
|
262
|
+
&::after {
|
|
263
|
+
display: none;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
&:is(:focus, :hover) {
|
|
267
|
+
background-color: var(--active-background-color);
|
|
268
|
+
color: var(--active-text-color);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
200
274
|
|
|
201
275
|
@include sv-flex(1, 0, auto);
|
|
202
276
|
display: flex;
|
|
@@ -213,15 +287,23 @@
|
|
|
213
287
|
--r--menu--item--hover--color: #{r-theme-val(topbar, item-hover-color)};
|
|
214
288
|
}
|
|
215
289
|
|
|
216
|
-
& >
|
|
290
|
+
& > button {
|
|
291
|
+
@include button-reset-style();
|
|
292
|
+
--radius: #{r-theme-val(topbar, dropdown-border-radius)};
|
|
293
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
& > a,
|
|
297
|
+
& > button {
|
|
217
298
|
@include sv-flex(1, 0, 100%);
|
|
299
|
+
align-items: center;
|
|
300
|
+
color: inherit;
|
|
218
301
|
display: flex;
|
|
219
|
-
padding: 1rem 0.2rem 1rem 1rem;
|
|
220
302
|
flex-direction: row;
|
|
221
|
-
align-items: center;
|
|
222
303
|
font-family: inherit;
|
|
223
304
|
font-weight: inherit;
|
|
224
|
-
|
|
305
|
+
justify-content: space-between;
|
|
306
|
+
padding: 1rem 0.2rem 1rem 1rem;
|
|
225
307
|
|
|
226
308
|
@include media-breakpoint-up($r-topbar-breakpoint) {
|
|
227
309
|
padding: 1rem 1rem;
|
|
@@ -235,18 +317,22 @@
|
|
|
235
317
|
|
|
236
318
|
// If there is no default hover color we assume there is also no variant
|
|
237
319
|
@if r-theme-val(topbar, item-hover-color) {
|
|
238
|
-
|
|
239
|
-
content: '';
|
|
240
|
-
position: absolute;
|
|
241
|
-
bottom: 0;
|
|
242
|
-
left: 0;
|
|
243
|
-
right: 0;
|
|
244
|
-
height: 8px;
|
|
320
|
+
&:is(a)::after {
|
|
245
321
|
background-color: var(--r--menu--item--hover--color);
|
|
246
322
|
border-top-left-radius: 0.2rem;
|
|
247
323
|
border-top-right-radius: 0.2rem;
|
|
324
|
+
bottom: 0;
|
|
325
|
+
content: '';
|
|
326
|
+
height: 8px;
|
|
327
|
+
left: 0;
|
|
328
|
+
position: absolute;
|
|
329
|
+
right: 0;
|
|
248
330
|
}
|
|
249
331
|
}
|
|
332
|
+
|
|
333
|
+
&:is(button) {
|
|
334
|
+
background-color: r-theme-val(topbar, dropdown-background-color);
|
|
335
|
+
}
|
|
250
336
|
}
|
|
251
337
|
}
|
|
252
338
|
}
|
|
@@ -257,8 +343,10 @@
|
|
|
257
343
|
|
|
258
344
|
// Current page item or current ancestor
|
|
259
345
|
&--selected,
|
|
260
|
-
&--ancestor
|
|
261
|
-
|
|
346
|
+
&--ancestor,
|
|
347
|
+
&:has(.topbar__sublist .topbar__item.topbar__item--selected) {
|
|
348
|
+
& > a,
|
|
349
|
+
& > button {
|
|
262
350
|
position: relative;
|
|
263
351
|
color: r-theme-val(topbar, item-active-color);
|
|
264
352
|
|
|
@@ -328,7 +416,8 @@
|
|
|
328
416
|
}
|
|
329
417
|
|
|
330
418
|
// Item divider
|
|
331
|
-
& + #{$item-selector}
|
|
419
|
+
& + #{$item-selector},
|
|
420
|
+
& > .topbar__list #{$item-selector} {
|
|
332
421
|
@if r-theme-val(topbar, item-divider-border) {
|
|
333
422
|
border-top: $onepixel solid r-theme-val(topbar, item-divider-border);
|
|
334
423
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// Draw a background grid in pure CSS
|
|
6
6
|
@mixin draw-grid($line-color: null, $border-color: null) {
|
|
7
7
|
@if $line-color {
|
|
8
|
-
background:
|
|
8
|
+
background:
|
|
9
|
+
linear-gradient(-90deg, $line-color $onepixel, transparent $onepixel),
|
|
9
10
|
linear-gradient($line-color $onepixel, transparent $onepixel),
|
|
10
11
|
linear-gradient(-90deg, $line-color $onepixel, transparent $onepixel),
|
|
11
12
|
linear-gradient($line-color $onepixel, transparent $onepixel),
|