Skip to content

Commit 2362e45

Browse files
committed
refactor: conditionally render Replace video button & fix redirection URLs
1 parent b2a4b9c commit 2362e45

11 files changed

Lines changed: 432 additions & 89 deletions

File tree

src/course-unit/CourseUnit.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ import legacySidebarMessages from './legacy-sidebar/messages';
7878
import unitInfoMessages from './unit-sidebar/unit-info/messages';
7979
import messages from './messages';
8080

81+
import { getApiWaffleFlagsUrl } from '../data/api';
82+
8183
let axiosMock;
8284
let store;
8385
let mockShowToast;
@@ -170,6 +172,11 @@ describe('<CourseUnit />', () => {
170172
axiosMock
171173
.onGet(getContentTaxonomyTagsCountApiUrl(blockId))
172174
.reply(200, 17);
175+
axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {
176+
waffle_flags: {
177+
'studio.enable_new_video_uploads_page': true,
178+
},
179+
});
173180
});
174181

175182
it('render CourseUnit component correctly', async () => {
Lines changed: 50 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,60 @@
1-
import { render, waitFor, act } from '@testing-library/react';
2-
import { configureStore } from '@reduxjs/toolkit';
3-
import { MemoryRouter } from 'react-router-dom';
4-
import { AppProvider } from '@edx/frontend-platform/react';
5-
import { IntlProvider } from '@edx/frontend-platform/i18n';
6-
import { initializeMockApp } from '@edx/frontend-platform';
1+
import { PartialEditorState, thunkActions } from '@src/editors/data/redux';
2+
import { initializeMocks, waitFor, act } from '@src/testUtils';
3+
import { editorRender, getEditorStore } from '@src/editors/editorTestRender';
74
import VideoEditorModal from './VideoEditorModal';
8-
import { thunkActions } from '../../../data/redux';
95

10-
jest.mock('../../../data/redux', () => ({
11-
...jest.requireActual('../../../data/redux'),
12-
thunkActions: {
13-
video: {
14-
loadVideoData: jest
15-
.fn()
16-
.mockImplementation(() => ({ type: 'MOCK_ACTION' })),
6+
thunkActions.video.loadVideoData = jest.fn().mockImplementation(() => ({ type: 'MOCK_ACTION' }));
7+
8+
const initialState: PartialEditorState = {
9+
app: {
10+
videos: {},
11+
learningContextId: 'course-v1:test+test+test',
12+
blockId: 'some-block-id',
13+
courseDetails: {},
14+
},
15+
requests: {
16+
uploadAsset: { status: 'inactive', response: {} as any },
17+
uploadTranscript: { status: 'inactive', response: {} as any },
18+
deleteTranscript: { status: 'inactive', response: {} as any },
19+
fetchVideos: { status: 'inactive', response: {} as any },
20+
},
21+
video: {
22+
videoSource: '',
23+
videoId: '',
24+
fallbackVideos: ['', ''],
25+
allowVideoDownloads: false,
26+
allowVideoSharing: { level: 'block', value: false },
27+
thumbnail: undefined,
28+
transcripts: [],
29+
selectedVideoTranscriptUrls: {},
30+
allowTranscriptDownloads: false,
31+
duration: {
32+
startTime: '00:00:00',
33+
stopTime: '00:00:00',
34+
total: '00:00:00',
1735
},
1836
},
19-
}));
37+
};
2038

2139
describe('VideoUploader', () => {
22-
let store;
23-
2440
beforeEach(async () => {
25-
store = configureStore({
26-
reducer: (state, action) => (action && action.newState ? action.newState : state),
27-
preloadedState: {
28-
app: {
29-
videos: [],
30-
learningContextId: 'course-v1:test+test+test',
31-
blockId: 'some-block-id',
32-
courseDetails: {},
33-
},
34-
requests: {
35-
uploadAsset: { status: 'inactive' },
36-
uploadTranscript: { status: 'inactive' },
37-
deleteTranscript: { status: 'inactive' },
38-
fetchVideos: { status: 'inactive' },
39-
},
40-
video: {
41-
videoSource: '',
42-
videoId: '',
43-
fallbackVideos: ['', ''],
44-
allowVideoDownloads: false,
45-
allowVideoSharing: { level: 'block', value: false },
46-
thumbnail: null,
47-
transcripts: [],
48-
transcriptHandlerUrl: '',
49-
selectedVideoTranscriptUrls: {},
50-
allowTranscriptDownloads: false,
51-
duration: {
52-
startTime: '00:00:00',
53-
stopTime: '00:00:00',
54-
total: '00:00:00',
55-
},
56-
},
57-
},
58-
});
59-
initializeMockApp({
60-
authenticatedUser: {
61-
userId: 3,
62-
username: 'test-user',
63-
administrator: true,
64-
roles: [],
65-
},
66-
});
41+
initializeMocks();
6742
});
6843

69-
const renderComponent = async () =>
70-
render(
71-
<AppProvider store={store} wrapWithRouter={false}>
72-
<IntlProvider locale="en">
73-
<MemoryRouter
74-
initialEntries={[
75-
'/some/path?selectedVideoId=id_1&selectedVideoUrl=https://video.com',
76-
]}
77-
>
78-
<VideoEditorModal isLibrary={false} />
79-
</MemoryRouter>
80-
</IntlProvider>
81-
</AppProvider>,
82-
);
44+
const renderComponent = () => (
45+
editorRender(
46+
<VideoEditorModal isLibrary={false} />,
47+
{
48+
routerProps: {
49+
initialEntries: ['/some/path?selectedVideoId=id_1&selectedVideoUrl=https://video.com'],
50+
},
51+
initialState,
52+
},
53+
)
54+
);
8355

8456
it('should render the component and call loadVideoData with correct parameters', async () => {
85-
await renderComponent();
57+
renderComponent();
8658
await waitFor(() => {
8759
expect(thunkActions.video.loadVideoData).toHaveBeenCalledWith(
8860
'id_1',
@@ -92,10 +64,11 @@ describe('VideoUploader', () => {
9264
});
9365

9466
it('should call loadVideoData again when isLoaded state changes', async () => {
95-
await renderComponent();
67+
renderComponent();
9668
await waitFor(() => {
97-
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(2);
69+
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(1);
9870
});
71+
const store = getEditorStore();
9972

10073
act(() => {
10174
store.dispatch({
@@ -111,7 +84,7 @@ describe('VideoUploader', () => {
11184
});
11285

11386
await waitFor(() => {
114-
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(3);
87+
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(2);
11588
});
11689
});
11790
});

src/editors/containers/VideoEditor/components/VideoEditorModal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
33
import { useLocation } from 'react-router-dom';
4+
import { useWaffleFlags } from '@src/data/apiHooks';
45
import * as appHooks from '../../../hooks';
56
import { thunkActions, selectors } from '../../../data/redux';
67
import VideoSettingsModal from './VideoSettingsModal';
@@ -41,6 +42,7 @@ const VideoEditorModal: React.FC<Props> = ({
4142
const isLoaded = useSelector(
4243
(state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
4344
);
45+
const { useNewVideoUploadsPage } = useWaffleFlags();
4446

4547
useEffect(() => {
4648
hooks.initialize(dispatch, selectedVideoId, selectedVideoUrl);
@@ -52,6 +54,7 @@ const VideoEditorModal: React.FC<Props> = ({
5254
onReturn: onSettingsReturn,
5355
isLibrary,
5456
onClose,
57+
useNewVideoUploadsPage,
5558
}}
5659
/>
5760
);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
screen,
3+
fireEvent,
4+
initializeMocks,
5+
} from '@src/testUtils';
6+
import { editorRender } from '@src/editors/editorTestRender';
7+
import VideoSettingsModal from '.';
8+
9+
const defaultProps = {
10+
onReturn: jest.fn(),
11+
isLibrary: false,
12+
onClose: jest.fn(),
13+
useNewVideoUploadsPage: true,
14+
};
15+
16+
const renderComponent = (overrideProps = {}) => {
17+
const customInitialState = {
18+
app: {
19+
videos: {},
20+
learningContextId: 'course-v1:test+test+test',
21+
blockId: 'some-block-id',
22+
courseDetails: {},
23+
},
24+
};
25+
26+
initializeMocks();
27+
28+
return {
29+
...editorRender(
30+
<VideoSettingsModal {...defaultProps} {...overrideProps} />,
31+
{ initialState: customInitialState },
32+
),
33+
};
34+
};
35+
36+
describe('<VideoSettingsModal />', () => {
37+
beforeEach(async () => {
38+
window.scrollTo = jest.fn();
39+
});
40+
41+
it('renders back button when useNewVideoUploadsPage is true and isLibrary is false', () => {
42+
renderComponent();
43+
expect(screen.getByRole('button', { name: /replace video/i })).toBeInTheDocument();
44+
});
45+
46+
it('does not render back button when isLibrary is true', () => {
47+
renderComponent({ isLibrary: true });
48+
expect(screen.queryByRole('button', { name: /replace video/i })).not.toBeInTheDocument();
49+
});
50+
51+
it('calls onReturn when back button is clicked', () => {
52+
renderComponent();
53+
fireEvent.click(screen.getByRole('button', { name: /replace video/i }));
54+
expect(defaultProps.onReturn).toHaveBeenCalled();
55+
});
56+
57+
it('calls onClose if onReturn is not provided', () => {
58+
renderComponent({ onReturn: null });
59+
fireEvent.click(screen.getByRole('button', { name: /replace video/i }));
60+
expect(defaultProps.onClose).toHaveBeenCalled();
61+
});
62+
});

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,22 @@ interface Props {
2121
onReturn: () => void;
2222
isLibrary: boolean;
2323
onClose?: (() => void) | null;
24+
useNewVideoUploadsPage?: boolean;
2425
}
2526

2627
const VideoSettingsModal: React.FC<Props> = ({
2728
onReturn,
2829
isLibrary,
2930
onClose,
31+
useNewVideoUploadsPage,
3032
}) => (
3133
<>
32-
{!isLibrary && (
34+
{!isLibrary && useNewVideoUploadsPage && (
3335
<Button
3436
variant="link"
3537
className="text-primary-500"
3638
size="sm"
37-
onClick={onClose || onReturn}
39+
onClick={onReturn || onClose}
3840
style={{
3941
textDecoration: 'none',
4042
marginLeft: '3px',

0 commit comments

Comments
 (0)