Skip to content

Commit 5f41db8

Browse files
feat: Enable the Video editor in content libraries [FC-0062] (#1319)
* feat: enable video editor in libraries * fix: a11y issue in video editor - URL and ID fields were combined * test: tests for video editor
1 parent 95521d3 commit 5f41db8

File tree

8 files changed

+143
-102
lines changed

8 files changed

+143
-102
lines changed

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap

Lines changed: 78 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default
1616
id="authoring.videoeditor.videoIdChangeAlert.message"
1717
/>
1818
</ErrorAlert>
19-
<Form.Group>
20-
<div
21-
className="border-primary-100 border-bottom pb-4"
22-
>
19+
<div
20+
className="border-primary-100 border-bottom pb-4"
21+
>
22+
<Form.Group>
2323
<Form.Control
2424
floatingLabel="Video ID"
2525
onBlur={[Function]}
@@ -35,6 +35,8 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default
3535
id="authoring.videoeditor.videoSource.videoId.feedback"
3636
/>
3737
</Form.Control.Feedback>
38+
</Form.Group>
39+
<Form.Group>
3840
<Form.Control
3941
floatingLabel="Video URL"
4042
onBlur={[Function]}
@@ -51,31 +53,33 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default
5153
id="authoring.videoeditor.videoSource.videoUrl.feedback"
5254
/>
5355
</Form.Control.Feedback>
54-
</div>
55-
<div
56-
className="mt-4"
57-
>
58-
<FormattedMessage
59-
defaultMessage="Fallback videos"
60-
description="Title for the fallback videos section"
61-
id="authoring.videoeditor.videoSource.fallbackVideo.title"
62-
/>
63-
</div>
64-
<div
65-
className="mt-3"
66-
>
67-
<FormattedMessage
68-
defaultMessage="To be sure all learners can access the video, edX
56+
</Form.Group>
57+
</div>
58+
<div
59+
className="mt-4"
60+
>
61+
<FormattedMessage
62+
defaultMessage="Fallback videos"
63+
description="Title for the fallback videos section"
64+
id="authoring.videoeditor.videoSource.fallbackVideo.title"
65+
/>
66+
</div>
67+
<div
68+
className="mt-3"
69+
>
70+
<FormattedMessage
71+
defaultMessage="To be sure all learners can access the video, edX
6972
recommends providing additional videos in both .mp4 and
7073
.webm formats. The first listed video compatible with the
7174
learner's device will play."
72-
description="Test explaining reason for fallback videos"
73-
id="authoring.videoeditor.videoSource.fallbackVideo.message"
74-
/>
75-
</div>
76-
<Form.Row
77-
className="mt-3.5 mx-0 flex-nowrap"
78-
>
75+
description="Test explaining reason for fallback videos"
76+
id="authoring.videoeditor.videoSource.fallbackVideo.message"
77+
/>
78+
</div>
79+
<Form.Row
80+
className="mt-3.5 mx-0 flex-nowrap"
81+
>
82+
<Form.Group>
7983
<Form.Control
8084
floatingLabel="Video URL"
8185
/>
@@ -87,10 +91,12 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default
8791
tooltipContent="Delete"
8892
tooltipPlacement="top"
8993
/>
90-
</Form.Row>
91-
<ActionRow
92-
className="mt-4.5"
93-
>
94+
</Form.Group>
95+
</Form.Row>
96+
<ActionRow
97+
className="mt-4.5"
98+
>
99+
<Form.Group>
94100
<Form.Checkbox
95101
checked={false}
96102
className="decorative-control-label"
@@ -132,9 +138,9 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default
132138
}
133139
/>
134140
</OverlayTrigger>
135-
<ActionRow.Spacer />
136-
</ActionRow>
137-
</Form.Group>
141+
</Form.Group>
142+
<ActionRow.Spacer />
143+
</ActionRow>
138144
<div
139145
className="my-4 border-primary-100 border-bottom"
140146
/>
@@ -169,10 +175,10 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh
169175
id="authoring.videoeditor.videoIdChangeAlert.message"
170176
/>
171177
</ErrorAlert>
172-
<Form.Group>
173-
<div
174-
className="border-primary-100 border-bottom pb-4"
175-
>
178+
<div
179+
className="border-primary-100 border-bottom pb-4"
180+
>
181+
<Form.Group>
176182
<Form.Control
177183
floatingLabel="Video ID"
178184
onBlur={[Function]}
@@ -188,6 +194,8 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh
188194
id="authoring.videoeditor.videoSource.videoId.feedback"
189195
/>
190196
</Form.Control.Feedback>
197+
</Form.Group>
198+
<Form.Group>
191199
<Form.Control
192200
floatingLabel="Video URL"
193201
onBlur={[Function]}
@@ -204,31 +212,33 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh
204212
id="authoring.videoeditor.videoSource.videoUrl.feedback"
205213
/>
206214
</Form.Control.Feedback>
207-
</div>
208-
<div
209-
className="mt-4"
210-
>
211-
<FormattedMessage
212-
defaultMessage="Fallback videos"
213-
description="Title for the fallback videos section"
214-
id="authoring.videoeditor.videoSource.fallbackVideo.title"
215-
/>
216-
</div>
217-
<div
218-
className="mt-3"
219-
>
220-
<FormattedMessage
221-
defaultMessage="To be sure all learners can access the video, edX
215+
</Form.Group>
216+
</div>
217+
<div
218+
className="mt-4"
219+
>
220+
<FormattedMessage
221+
defaultMessage="Fallback videos"
222+
description="Title for the fallback videos section"
223+
id="authoring.videoeditor.videoSource.fallbackVideo.title"
224+
/>
225+
</div>
226+
<div
227+
className="mt-3"
228+
>
229+
<FormattedMessage
230+
defaultMessage="To be sure all learners can access the video, edX
222231
recommends providing additional videos in both .mp4 and
223232
.webm formats. The first listed video compatible with the
224233
learner's device will play."
225-
description="Test explaining reason for fallback videos"
226-
id="authoring.videoeditor.videoSource.fallbackVideo.message"
227-
/>
228-
</div>
229-
<Form.Row
230-
className="mt-3.5 mx-0 flex-nowrap"
231-
>
234+
description="Test explaining reason for fallback videos"
235+
id="authoring.videoeditor.videoSource.fallbackVideo.message"
236+
/>
237+
</div>
238+
<Form.Row
239+
className="mt-3.5 mx-0 flex-nowrap"
240+
>
241+
<Form.Group>
232242
<Form.Control
233243
floatingLabel="Video URL"
234244
/>
@@ -240,10 +250,12 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh
240250
tooltipContent="Delete"
241251
tooltipPlacement="top"
242252
/>
243-
</Form.Row>
244-
<ActionRow
245-
className="mt-4.5"
246-
>
253+
</Form.Group>
254+
</Form.Row>
255+
<ActionRow
256+
className="mt-4.5"
257+
>
258+
<Form.Group>
247259
<Form.Checkbox
248260
checked={false}
249261
className="decorative-control-label"
@@ -285,9 +297,9 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh
285297
}
286298
/>
287299
</OverlayTrigger>
288-
<ActionRow.Spacer />
289-
</ActionRow>
290-
</Form.Group>
300+
</Form.Group>
301+
<ActionRow.Spacer />
302+
</ActionRow>
291303
<div
292304
className="my-4 border-primary-100 border-bottom"
293305
/>

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ const VideoSourceWidget = ({
6969
>
7070
<FormattedMessage {...messages.videoIdChangeAlert} />
7171
</ErrorAlert>
72-
<Form.Group>
73-
<div className="border-primary-100 border-bottom pb-4">
72+
73+
<div className="border-primary-100 border-bottom pb-4">
74+
<Form.Group>
7475
<Form.Control
7576
floatingLabel={intl.formatMessage(messages.videoIdLabel)}
7677
onChange={videoId.onChange}
@@ -80,6 +81,8 @@ const VideoSourceWidget = ({
8081
<Form.Control.Feedback className="text-primary-300 mb-4">
8182
<FormattedMessage {...messages.videoIdFeedback} />
8283
</Form.Control.Feedback>
84+
</Form.Group>
85+
<Form.Group>
8386
<Form.Control
8487
floatingLabel={intl.formatMessage(messages.videoUrlLabel)}
8588
onChange={source.onChange}
@@ -89,15 +92,17 @@ const VideoSourceWidget = ({
8992
<Form.Control.Feedback className="text-primary-300">
9093
<FormattedMessage {...messages.videoUrlFeedback} />
9194
</Form.Control.Feedback>
92-
</div>
93-
<div className="mt-4">
94-
<FormattedMessage {...messages.fallbackVideoTitle} />
95-
</div>
96-
<div className="mt-3">
97-
<FormattedMessage {...messages.fallbackVideoMessage} />
98-
</div>
99-
{fallbackVideos.formValue.length > 0 ? fallbackVideos.formValue.map((videoUrl, index) => (
100-
<Form.Row className="mt-3.5 mx-0 flex-nowrap">
95+
</Form.Group>
96+
</div>
97+
<div className="mt-4">
98+
<FormattedMessage {...messages.fallbackVideoTitle} />
99+
</div>
100+
<div className="mt-3">
101+
<FormattedMessage {...messages.fallbackVideoMessage} />
102+
</div>
103+
{fallbackVideos.formValue.length > 0 ? fallbackVideos.formValue.map((videoUrl, index) => (
104+
<Form.Row className="mt-3.5 mx-0 flex-nowrap">
105+
<Form.Group>
101106
<Form.Control
102107
floatingLabel={intl.formatMessage(messages.fallbackVideoLabel)}
103108
onChange={fallbackVideos.onChange(index)}
@@ -113,9 +118,11 @@ const VideoSourceWidget = ({
113118
alt={intl.formatMessage(messages.deleteFallbackVideo)}
114119
onClick={() => deleteFallbackVideo(videoUrl)}
115120
/>
116-
</Form.Row>
117-
)) : null}
118-
<ActionRow className="mt-4.5">
121+
</Form.Group>
122+
</Form.Row>
123+
)) : null}
124+
<ActionRow className="mt-4.5">
125+
<Form.Group>
119126
<Form.Checkbox
120127
checked={allowDownload.local}
121128
className="decorative-control-label"
@@ -136,9 +143,10 @@ const VideoSourceWidget = ({
136143
>
137144
<Icon src={InfoOutline} style={{ height: '16px', width: '16px' }} />
138145
</OverlayTrigger>
139-
<ActionRow.Spacer />
140-
</ActionRow>
141-
</Form.Group>
146+
</Form.Group>
147+
<ActionRow.Spacer />
148+
</ActionRow>
149+
142150
<div className="my-4 border-primary-100 border-bottom" />
143151
<Button
144152
className="text-primary-500 font-weight-bold pl-0"

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,9 @@ describe('VideoSourceWidget', () => {
100100
.props.onBlur).toEqual(expected);
101101
});
102102
test('updateVideoURL is tied to url field onBlur', () => {
103-
const { onBlur } = el
104-
// eslint-disable-next-line
105-
.shallowWrapper.props.children[1].props.children[0].props.children[2].props;
106-
onBlur('onBlur event');
103+
const control = el.shallowWrapper.props.children[1].props.children[1].props.children[0];
104+
expect(control.props.floatingLabel).toEqual('Video URL');
105+
control.props.onBlur('onBlur event');
107106
expect(hook.updateVideoURL).toHaveBeenCalledWith('onBlur event', '');
108107
});
109108
});

src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ const VideoSettingsModal: React.FC<Props> = ({
4848
<SocialShareWidget />
4949
)}
5050
<ThumbnailWidget />
51-
<TranscriptWidget />
51+
{!isLibrary && ( // Since content libraries v2 don't support static assets yet, we can't include transcripts.
52+
<TranscriptWidget />
53+
)}
5254
<DurationWidget />
5355
<HandoutWidget />
5456
<LicenseWidget />

src/library-authoring/add-content/AddContentWorkflow.test.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jest.spyOn(editorCmsApi as any, 'fetchBlockById').mockImplementation(
3737
);
3838
jest.spyOn(textEditorHooks, 'getContent').mockImplementation(() => () => '<p>Edited HTML content</p>');
3939
jest.mock('frontend-components-tinymce-advanced-plugins', () => ({ a11ycheckerCss: '' }));
40+
const saveSpy = jest.spyOn(editorCmsApi as any, 'saveBlock');
4041

4142
const { libraryId } = mockContentLibrary;
4243
const renderOpts = {
@@ -74,7 +75,7 @@ describe('AddContentWorkflow test', () => {
7475
// using TinyMCE to enter some new HTML.
7576

7677
// Mock the save() REST API method:
77-
const saveSpy = jest.spyOn(editorCmsApi as any, 'saveBlock').mockImplementationOnce(async () => ({
78+
saveSpy.mockReset().mockImplementationOnce(async () => ({
7879
status: 200, data: { id: mockXBlockFields.usageKeyNewHtml },
7980
}));
8081

@@ -109,31 +110,43 @@ describe('AddContentWorkflow test', () => {
109110
fireEvent.change(inputA, { target: { value: '123456' } });
110111

111112
// Mock the save() REST API method:
112-
const saveSpy = jest.spyOn(editorCmsApi as any, 'saveBlock').mockImplementationOnce(async () => ({
113+
saveSpy.mockReset().mockImplementationOnce(async () => ({
113114
status: 200, data: { id: mockXBlockFields.usageKeyNewProblem },
114115
}));
115116

116117
// Click Save
117118
const saveButton = screen.getByLabelText('Save changes and return to learning context');
118119
fireEvent.click(saveButton);
119-
expect(saveSpy).toHaveBeenCalledTimes(2); // TODO: why is this called twice?
120+
expect(saveSpy).toHaveBeenCalledTimes(1);
120121
});
121122

122123
it('can create a Video component', async () => {
123-
const { mockShowToast } = initializeMocks();
124+
initializeMocks();
124125
render(<LibraryLayout />, renderOpts);
125126

126127
// Click "New [Component]"
127128
const newComponentButton = await screen.findByRole('button', { name: /New/ });
128129
fireEvent.click(newComponentButton);
129130

130-
// Pre-condition - the success toast is NOT shown yet:
131-
expect(mockShowToast).not.toHaveBeenCalled();
132-
133131
// Click "Video" to create a video component
134132
fireEvent.click(await screen.findByRole('button', { name: /Video/ }));
135133

136-
// We haven't yet implemented the video editor, so we expect only a toast to appear
137-
await waitFor(() => expect(mockShowToast).toHaveBeenCalledWith('Content created successfully.'));
134+
// Then the editor should open - this is the default title of a blank video in our mock
135+
expect(await screen.findByRole('heading', { name: /New Video/ })).toBeInTheDocument();
136+
137+
// Enter the video URL
138+
const urlInput = await screen.findByRole('textbox', { name: 'Video URL' });
139+
fireEvent.click(urlInput);
140+
fireEvent.change(urlInput, { target: { value: 'https://www.youtube.com/watch?v=9KIIlWS4mkg' } });
141+
142+
// Mock the save() REST API method:
143+
saveSpy.mockReset().mockImplementationOnce(async () => ({
144+
status: 200, data: { id: mockXBlockFields.usageKeyNewVideo },
145+
}));
146+
147+
// Click Save
148+
const saveButton = screen.getByLabelText('Save changes and return to learning context');
149+
fireEvent.click(saveButton);
150+
expect(saveSpy).toHaveBeenCalledTimes(1);
138151
});
139152
});

0 commit comments

Comments
 (0)