Skip to content

refactor: conditionally render Replace video button & fix redirection URLs #1915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
70a53bc
fix: update navigation paths to include 'authoring' prefix
bra-i-am May 6, 2025
60fb036
feat: add waffleFlags state and actions for managing feature flags
bra-i-am May 7, 2025
6fb295f
feat: integrate waffle flags to conditionally render video upload button
bra-i-am May 7, 2025
fafc4da
fix: adjust order of return handlers in VideoEditorModal and VideoSet…
bra-i-am May 7, 2025
1f96592
feat: add waffle flag integration for new video uploads in VideoEdito…
bra-i-am May 7, 2025
1523a37
feat: update initialize actions to include fetchWaffleFlags and courseId
bra-i-am May 7, 2025
9095e88
fix: adjust formatting of JSX elements in VideoSettingsModal
bra-i-am May 7, 2025
9ede52d
fix: remove unused useSelector import in VideoSettingsModal
bra-i-am May 7, 2025
d0692b6
fix: reorder fetchWaffleFlags calls in initialize function for correc…
bra-i-am May 8, 2025
6e21b59
fix: update initialize test to fetch block and unit with correct test…
bra-i-am May 8, 2025
ff741ff
fix: remove redundant comments and streamline fetchWaffleFlags mock i…
bra-i-am May 8, 2025
582b10a
fix: remove unnecessary blank lines in app thunkActions tests for cle…
bra-i-am May 8, 2025
552fde4
fix: add mock for getApiWaffleFlagsUrl in CourseUnit tests
bra-i-am May 8, 2025
5265617
fix: correct import statement for getApiWaffleFlagsUrl and format waf…
bra-i-am May 8, 2025
62b84f9
test: add unit tests for VideoSettingsModal component
bra-i-am May 8, 2025
15daa7f
test: add unit tests for hooks module in VideoGallery
bra-i-am May 8, 2025
64c5f9d
test: add unit tests for hooks module in VideoGallery
bra-i-am May 8, 2025
fe88fa4
test: add unit tests for hooks module in VideoUploadEditor
bra-i-am May 8, 2025
76b1283
fix: remove mfe prefix from navigation path
bra-i-am Jun 3, 2025
bac374a
fix: update navigation paths in hooks tests to remove 'authoring' prefix
bra-i-am Jun 3, 2025
4fb2223
refactor: remove waffleFlags from state management and related selectors
bra-i-am Jun 16, 2025
d0bd3c2
test: integrate QueryClientProvider in VideoEditorModal tests
bra-i-am Jun 16, 2025
8f863b7
refactor: update VideoSettingsModal tests to use new rendering method…
bra-i-am Jun 18, 2025
f77c9dd
refactor: update onReturn prop type in VideoSettingsModal to remove n…
bra-i-am Jun 18, 2025
aac0e17
refactor: update import paths and enhance createStore mock in tests
bra-i-am Jun 18, 2025
6406f8f
refactor: adjust onSettingsReturn assignment order for clarity
bra-i-am Jun 18, 2025
785b306
test: enhance VideoEditorModal mock initialization
bra-i-am Jun 24, 2025
16ca603
refactor: change editorRender export to a const declaration
bra-i-am Jun 24, 2025
7fbd878
refactor: replace render with editorRender in VideoSettingsModal tests
bra-i-am Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import sidebarMessages from './sidebar/messages';
import messages from './messages';
import { mockWaffleFlags } from '../data/apiHooks.mock';

import { getApiWaffleFlagsUrl } from '../data/api';

let axiosMock;
let store;
let queryClient;
Expand Down Expand Up @@ -158,6 +160,12 @@ describe('<CourseUnit />', () => {
axiosMock
.onGet(getContentTaxonomyTagsCountApiUrl(blockId))
.reply(200, 17);
axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {
waffle_flags: {
'studio.enable_new_video_uploads_page': true,
'studio.enable_new_text_editor': false,
},
});
});

it('render CourseUnit component correctly', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,87 +1,58 @@
import { render, waitFor, act } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import { MemoryRouter } from 'react-router-dom';
import { AppProvider } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
import { thunkActions } from '@src/editors/data/redux';
import { initializeMocks, waitFor, act } from '@src/testUtils';
import editorRender, { getEditorStore, PartialEditorState } from '@src/editors/editorTestRender';
import VideoEditorModal from './VideoEditorModal';
import { thunkActions } from '../../../data/redux';

jest.mock('../../../data/redux', () => ({
...jest.requireActual('../../../data/redux'),
thunkActions: {
video: {
loadVideoData: jest
.fn()
.mockImplementation(() => ({ type: 'MOCK_ACTION' })),
thunkActions.video.loadVideoData = jest.fn().mockImplementation(() => ({ type: 'MOCK_ACTION' }));

const initialState: PartialEditorState = {
app: {
videos: [],
learningContextId: 'course-v1:test+test+test',
blockId: 'some-block-id',
courseDetails: {},
},
requests: {
uploadAsset: { status: 'inactive', response: {} as any },
uploadTranscript: { status: 'inactive', response: {} as any },
deleteTranscript: { status: 'inactive', response: {} as any },
fetchVideos: { status: 'inactive', response: {} as any },
},
video: {
videoSource: '',
videoId: '',
fallbackVideos: ['', ''],
allowVideoDownloads: false,
allowVideoSharing: { level: 'block', value: false },
thumbnail: null,
transcripts: [],
selectedVideoTranscriptUrls: {},
allowTranscriptDownloads: false,
duration: {
startTime: '00:00:00',
stopTime: '00:00:00',
total: '00:00:00',
},
},
}));
};

describe('VideoUploader', () => {
let store;

beforeEach(async () => {
store = configureStore({
reducer: (state, action) => (action && action.newState ? action.newState : state),
preloadedState: {
app: {
videos: [],
learningContextId: 'course-v1:test+test+test',
blockId: 'some-block-id',
courseDetails: {},
},
requests: {
uploadAsset: { status: 'inactive' },
uploadTranscript: { status: 'inactive' },
deleteTranscript: { status: 'inactive' },
fetchVideos: { status: 'inactive' },
},
video: {
videoSource: '',
videoId: '',
fallbackVideos: ['', ''],
allowVideoDownloads: false,
allowVideoSharing: { level: 'block', value: false },
thumbnail: null,
transcripts: [],
transcriptHandlerUrl: '',
selectedVideoTranscriptUrls: {},
allowTranscriptDownloads: false,
duration: {
startTime: '00:00:00',
stopTime: '00:00:00',
total: '00:00:00',
},
},
},
});
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'test-user',
administrator: true,
roles: [],
},
});
initializeMocks();
});

const renderComponent = async () => render(
<AppProvider store={store} wrapWithRouter={false}>
<IntlProvider locale="en">
<MemoryRouter
initialEntries={[
'/some/path?selectedVideoId=id_1&selectedVideoUrl=https://video.com',
]}
>
<VideoEditorModal isLibrary={false} />
</MemoryRouter>
</IntlProvider>
</AppProvider>,
const renderComponent = () => editorRender(
<VideoEditorModal isLibrary={false} />,
{
routerProps: {
initialEntries: ['/some/path?selectedVideoId=id_1&selectedVideoUrl=https://video.com'],
},
initialState,
},
);

it('should render the component and call loadVideoData with correct parameters', async () => {
await renderComponent();
renderComponent();
await waitFor(() => {
expect(thunkActions.video.loadVideoData).toHaveBeenCalledWith(
'id_1',
Expand All @@ -91,10 +62,11 @@ describe('VideoUploader', () => {
});

it('should call loadVideoData again when isLoaded state changes', async () => {
await renderComponent();
renderComponent();
await waitFor(() => {
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(2);
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(1);
});
const store = getEditorStore();

act(() => {
store.dispatch({
Expand All @@ -110,7 +82,7 @@ describe('VideoUploader', () => {
});

await waitFor(() => {
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(3);
expect(thunkActions.video.loadVideoData).toHaveBeenCalledTimes(2);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { useWaffleFlags } from '@src/data/apiHooks';
import * as appHooks from '../../../hooks';
import { thunkActions, selectors } from '../../../data/redux';
import VideoSettingsModal from './VideoSettingsModal';
Expand Down Expand Up @@ -41,6 +42,7 @@ const VideoEditorModal: React.FC<Props> = ({
const isLoaded = useSelector(
(state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
);
const { useNewVideoUploadsPage } = useWaffleFlags();

useEffect(() => {
hooks.initialize(dispatch, selectedVideoId, selectedVideoUrl);
Expand All @@ -51,6 +53,7 @@ const VideoEditorModal: React.FC<Props> = ({
onReturn: onSettingsReturn,
isLibrary,
onClose,
useNewVideoUploadsPage,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
screen, fireEvent, initializeMocks,
} from '@src/testUtils';
import editorRender from '@src/editors/editorTestRender';
import VideoSettingsModal from '.';

const defaultProps = {
onReturn: jest.fn(),
isLibrary: false,
onClose: jest.fn(),
useNewVideoUploadsPage: true,
};

const renderComponent = (overrideProps = {}) => {
const customInitialState = {
app: {
videos: [],
learningContextId: 'course-v1:test+test+test',
blockId: 'some-block-id',
courseDetails: {},
},
};

initializeMocks();

return {
...editorRender(
<VideoSettingsModal {...defaultProps} {...overrideProps} />,
{ initialState: customInitialState },
),
};
};

describe('<VideoSettingsModal />', () => {
beforeEach(async () => {
window.scrollTo = jest.fn();
});

it('renders back button when useNewVideoUploadsPage is true and isLibrary is false', () => {
renderComponent();
expect(screen.getByRole('button', { name: /replace video/i })).toBeInTheDocument();
});

it('does not render back button when isLibrary is true', () => {
renderComponent({ isLibrary: true });
expect(screen.queryByRole('button', { name: /replace video/i })).not.toBeInTheDocument();
});

it('calls onReturn when back button is clicked', () => {
renderComponent();
fireEvent.click(screen.getByRole('button', { name: /replace video/i }));
expect(defaultProps.onReturn).toHaveBeenCalled();
});

it('calls onClose if onReturn is not provided', () => {
renderComponent({ onReturn: null });
fireEvent.click(screen.getByRole('button', { name: /replace video/i }));
expect(defaultProps.onClose).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,22 @@ interface Props {
onReturn: () => void;
isLibrary: boolean;
onClose?: (() => void) | null;
useNewVideoUploadsPage?: boolean;
}

const VideoSettingsModal: React.FC<Props> = ({
onReturn,
isLibrary,
onClose,
useNewVideoUploadsPage,
}) => (
<>
{!isLibrary && (
{!isLibrary && useNewVideoUploadsPage && (
<Button
variant="link"
className="text-primary-500"
size="sm"
onClick={onClose || onReturn}
onClick={onReturn || onClose}
style={{
textDecoration: 'none',
marginLeft: '3px',
Expand Down
Loading