diff --git a/src/editors/containers/EditorContainer/hooks.js b/src/editors/containers/EditorContainer/hooks.js
index b0385b6e5..caae0263d 100644
--- a/src/editors/containers/EditorContainer/hooks.js
+++ b/src/editors/containers/EditorContainer/hooks.js
@@ -27,7 +27,8 @@ export const handleSaveClicked = ({
returnFunction,
}) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
- const destination = returnFunction ? '' : useSelector(selectors.app.returnUrl);
+ const returnUrl = useSelector(selectors.app.returnUrl);
+ const destination = returnFunction ? '' : returnUrl;
// eslint-disable-next-line react-hooks/rules-of-hooks
const analytics = useSelector(selectors.app.analytics);
@@ -54,10 +55,12 @@ export const handleCancel = ({ onClose, returnFunction }) => {
if (onClose) {
return onClose;
}
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const returnUrl = useSelector(selectors.app.returnUrl);
return navigateCallback({
returnFunction,
// eslint-disable-next-line react-hooks/rules-of-hooks
- destination: returnFunction ? '' : useSelector(selectors.app.returnUrl),
+ destination: returnFunction ? '' : returnUrl,
analyticsEvent: analyticsEvt.editorCancelClick,
// eslint-disable-next-line react-hooks/rules-of-hooks
analytics: useSelector(selectors.app.analytics),
diff --git a/src/editors/containers/VideoGallery/hooks.js b/src/editors/containers/VideoGallery/hooks.js
index 970342abc..3c8c3ec24 100644
--- a/src/editors/containers/VideoGallery/hooks.js
+++ b/src/editors/containers/VideoGallery/hooks.js
@@ -1,16 +1,13 @@
import React from 'react';
import { useSelector } from 'react-redux';
+
import * as module from './hooks';
import messages from './messages';
import * as appHooks from '../../hooks';
import { selectors } from '../../data/redux';
import analyticsEvt from '../../data/constants/analyticsEvt';
import {
- filterKeys,
- filterMessages,
- sortKeys,
- sortMessages,
- sortFunctions,
+ filterKeys, sortFunctions, sortKeys, sortMessages,
} from './utils';
export const {
@@ -18,28 +15,19 @@ export const {
navigateTo,
} = appHooks;
-export const state = {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- highlighted: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- searchString: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- showSelectVideoError: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- showSizeError: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- sortBy: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- filertBy: (val) => React.useState(val),
- // eslint-disable-next-line react-hooks/rules-of-hooks
- hideSelectedVideos: (val) => React.useState(val),
-};
+export const useSearchAndSortProps = () => {
+ const [searchString, setSearchString] = React.useState('');
+ const [sortBy, setSortBy] = React.useState(sortKeys.dateNewest);
+ const [filterBy, setFilterBy] = React.useState([]);
+ const [hideSelectedVideos, setHideSelectedVideos] = React.useState(false);
-export const searchAndSortProps = () => {
- const [searchString, setSearchString] = module.state.searchString('');
- const [sortBy, setSortBy] = module.state.sortBy(sortKeys.dateNewest);
- const [filterBy, setFilterBy] = module.state.filertBy(filterKeys.videoStatus);
- const [hideSelectedVideos, setHideSelectedVideos] = module.state.hideSelectedVideos(false);
+ const handleFilter = (key) => () => {
+ if (filterBy.includes(key)) {
+ setFilterBy(filterBy.filter(item => item !== key));
+ } else {
+ setFilterBy([...filterBy, key]);
+ }
+ };
return {
searchString,
@@ -50,9 +38,7 @@ export const searchAndSortProps = () => {
sortKeys,
sortMessages,
filterBy,
- onFilterClick: (key) => () => setFilterBy(key),
- filterKeys,
- filterMessages,
+ onFilterClick: handleFilter,
showSwitch: false,
hideSelectedVideos,
switchMessage: messages.hideSelectedCourseVideosSwitchLabel,
@@ -60,15 +46,23 @@ export const searchAndSortProps = () => {
};
};
-export const filterListBySearch = ({ searchString, videoList }) => (
- videoList.filter(({ displayName }) => displayName.toLowerCase().includes(searchString.toLowerCase()))
+export const filterListBySearch = ({
+ searchString,
+ videoList,
+}) => (
+ videoList.filter(({ displayName }) => displayName.toLowerCase()
+ .includes(searchString.toLowerCase()))
);
-export const filterListByStatus = ({ statusFilter, videoList }) => {
- if (statusFilter === filterKeys.videoStatus) {
+export const filterListByStatus = ({
+ statusFilter,
+ videoList,
+}) => {
+ if (statusFilter.length === 0) {
return videoList;
}
- return videoList.filter(({ status }) => status === statusFilter);
+ return videoList.filter(({ status }) => statusFilter.map(key => filterKeys[key])
+ .includes(status));
};
export const filterListByHideSelectedCourse = ({ videoList }) => (
@@ -96,20 +90,24 @@ export const filterList = ({
return filteredList.sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest]);
};
-export const videoListProps = ({ searchSortProps, videos }) => {
- const [highlighted, setHighlighted] = module.state.highlighted(null);
+export const useVideoListProps = ({
+ searchSortProps,
+ videos,
+}) => {
+ const [highlighted, setHighlighted] = React.useState(null);
const [
showSelectVideoError,
setShowSelectVideoError,
- ] = module.state.showSelectVideoError(false);
+ ] = React.useState(false);
const [
showSizeError,
setShowSizeError,
- ] = module.state.showSizeError(false);
- const filteredList = module.filterList({ ...searchSortProps, videos });
- // eslint-disable-next-line react-hooks/rules-of-hooks
+ ] = React.useState(false);
+ const filteredList = module.filterList({
+ ...searchSortProps,
+ videos,
+ });
const learningContextId = useSelector(selectors.app.learningContextId);
- // eslint-disable-next-line react-hooks/rules-of-hooks
const blockId = useSelector(selectors.app.blockId);
return {
galleryError: {
@@ -147,26 +145,15 @@ export const videoListProps = ({ searchSortProps, videos }) => {
};
};
-export const fileInputProps = () => {
- const click = module.handleVideoUpload();
- return {
- click,
- };
-};
-
-export const handleVideoUpload = () => {
- // eslint-disable-next-line react-hooks/rules-of-hooks
+export const useVideoUploadHandler = () => {
const learningContextId = useSelector(selectors.app.learningContextId);
- // eslint-disable-next-line react-hooks/rules-of-hooks
const blockId = useSelector(selectors.app.blockId);
return () => navigateTo(`/course/${learningContextId}/editor/video_upload/${blockId}`);
};
-export const handleCancel = () => (
+export const useCancelHandler = () => (
navigateCallback({
- // eslint-disable-next-line react-hooks/rules-of-hooks
destination: useSelector(selectors.app.returnUrl),
- // eslint-disable-next-line react-hooks/rules-of-hooks
analytics: useSelector(selectors.app.analytics),
analyticsEvent: analyticsEvt.videoGalleryCancelClick,
})
@@ -180,7 +167,7 @@ export const buildVideos = ({ rawVideos }) => {
id: video.edx_video_id,
displayName: video.client_video_id,
externalUrl: video.course_video_image_url,
- dateAdded: video.created,
+ dateAdded: new Date(video.created),
locked: false,
thumbnail: video.course_video_image_url,
status: video.status,
@@ -204,16 +191,19 @@ export const getstatusBadgeVariant = ({ status }) => {
}
};
-export const videoProps = ({ videos }) => {
- const searchSortProps = module.searchAndSortProps();
- const videoList = module.videoListProps({ searchSortProps, videos });
+export const useVideoProps = ({ videos }) => {
+ const searchSortProps = useSearchAndSortProps();
+ const videoList = useVideoListProps({
+ searchSortProps,
+ videos,
+ });
const {
galleryError,
galleryProps,
inputError,
selectBtnProps,
} = videoList;
- const fileInput = module.fileInputProps();
+ const fileInput = { click: useVideoUploadHandler() };
return {
galleryError,
@@ -226,8 +216,8 @@ export const videoProps = ({ videos }) => {
};
export default {
- videoProps,
+ useVideoProps,
buildVideos,
- handleCancel,
- handleVideoUpload,
+ useCancelHandler,
+ useVideoUploadHandler,
};
diff --git a/src/editors/containers/VideoGallery/hooks.test.js b/src/editors/containers/VideoGallery/hooks.test.js
deleted file mode 100644
index 3015ad1f8..000000000
--- a/src/editors/containers/VideoGallery/hooks.test.js
+++ /dev/null
@@ -1,312 +0,0 @@
-import * as reactRedux from 'react-redux';
-import * as hooks from './hooks';
-import { filterKeys, sortKeys } from './utils';
-import { MockUseState } from '../../../testUtils';
-import { keyStore } from '../../utils';
-import * as appHooks from '../../hooks';
-import { selectors } from '../../data/redux';
-import analyticsEvt from '../../data/constants/analyticsEvt';
-
-jest.mock('react', () => ({
- ...jest.requireActual('react'),
- useRef: jest.fn(val => ({ current: val })),
- useEffect: jest.fn(),
- useCallback: (cb, prereqs) => ({ cb, prereqs }),
-}));
-
-jest.mock('react-redux', () => {
- const dispatchFn = jest.fn();
- return {
- ...jest.requireActual('react-redux'),
- dispatch: dispatchFn,
- useDispatch: jest.fn(() => dispatchFn),
- useSelector: jest.fn(),
- };
-});
-
-jest.mock('../../data/redux', () => ({
- selectors: {
- app: {
- returnUrl: 'returnUrl',
- analytics: 'analytics',
- },
- },
-}));
-
-jest.mock('../../hooks', () => ({
- ...jest.requireActual('../../hooks'),
- navigateCallback: jest.fn((args) => ({ navigateCallback: args })),
- navigateTo: jest.fn((args) => ({ navigateTo: args })),
-}));
-
-const state = new MockUseState(hooks);
-const hookKeys = keyStore(hooks);
-let hook;
-const testValue = 'testVALUEVALID';
-
-describe('VideoGallery hooks', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
- describe('state hooks', () => {
- state.testGetter(state.keys.highlighted);
- state.testGetter(state.keys.searchString);
- state.testGetter(state.keys.showSelectVideoError);
- state.testGetter(state.keys.showSizeError);
- state.testGetter(state.keys.sortBy);
- state.testGetter(state.keys.filertBy);
- state.testGetter(state.keys.hideSelectedVideos);
- });
- describe('using state', () => {
- beforeEach(() => { state.mock(); });
- afterEach(() => { state.restore(); });
-
- describe('searchAndSortProps', () => {
- beforeEach(() => {
- hook = hooks.searchAndSortProps();
- });
- it('returns searchString value, initialized to an empty string', () => {
- expect(state.stateVals.searchString).toEqual(hook.searchString);
- expect(state.stateVals.searchString).toEqual('');
- });
- it('returns highlighted value, initialized to dateNewest', () => {
- expect(state.stateVals.sortBy).toEqual(hook.sortBy);
- expect(state.stateVals.sortBy).toEqual(sortKeys.dateNewest);
- });
- test('onSearchChange sets searchString with event target value', () => {
- hook.onSearchChange({ target: { value: testValue } });
- expect(state.setState.searchString).toHaveBeenCalledWith(testValue);
- });
- test('clearSearchString sets search string to empty string', () => {
- hook.clearSearchString();
- expect(state.setState.searchString).toHaveBeenCalledWith('');
- });
- test('onSortClick takes a key and returns callback to set sortBY to that key', () => {
- hook.onSortClick(testValue);
- expect(state.setState.sortBy).not.toHaveBeenCalled();
- hook.onSortClick(testValue)();
- expect(state.setState.sortBy).toHaveBeenCalledWith(testValue);
- });
- });
- describe('filterListBySearch', () => {
- const matching = [
- 'test',
- 'TEst',
- 'eeees',
- 'essSSSS',
- ];
- const notMatching = ['bad', 'other', 'bad stuff'];
- const searchString = 'eS';
- test('returns list filtered lowercase by displayName', () => {
- const filter = jest.fn(cb => ({ filter: cb }));
- hook = hooks.filterListBySearch({ searchString, videoList: { filter } });
- expect(filter).toHaveBeenCalled();
- const [[filterCb]] = filter.mock.calls;
- matching.forEach(val => expect(filterCb({ displayName: val })).toEqual(true));
- notMatching.forEach(val => expect(filterCb({ displayName: val })).toEqual(false));
- });
- });
- describe('buildVideos', () => {
- const rawVideos = [
- {
- edx_video_id: 'id_1',
- client_video_id: 'client_id_1',
- course_video_image_url: 'course_video_image_url_1',
- created: 'created_1',
- status: 'status_1',
- duration: 1,
- transcripts: [],
- },
- {
- edx_video_id: 'id_2',
- client_video_id: 'client_id_2',
- course_video_image_url: 'course_video_image_url_2',
- created: 'created_2',
- status: 'status_2',
- duration: 2,
- transcripts: [],
- },
- ];
- const expectedValues = [
- {
- id: 'id_1',
- displayName: 'client_id_1',
- externalUrl: 'course_video_image_url_1',
- dateAdded: 'created_1',
- locked: false,
- thumbnail: 'course_video_image_url_1',
- status: 'status_1',
- statusBadgeVariant: null,
- duration: 1,
- transcripts: [],
- },
- {
- id: 'id_2',
- displayName: 'client_id_2',
- externalUrl: 'course_video_image_url_2',
- dateAdded: 'created_2',
- locked: false,
- thumbnail: 'course_video_image_url_2',
- status: 'status_2',
- statusBadgeVariant: null,
- duration: 2,
- transcripts: [],
- },
- ];
- test('return the expected values', () => {
- const values = hooks.buildVideos({ rawVideos });
- expect(values).toEqual(expectedValues);
- });
- });
- describe('getstatusBadgeVariant', () => {
- test('return the expected values', () => {
- let value = hooks.getstatusBadgeVariant({ status: filterKeys.failed });
- expect(value).toEqual('danger');
- value = hooks.getstatusBadgeVariant({ status: filterKeys.uploading });
- expect(value).toEqual('light');
- value = hooks.getstatusBadgeVariant({ status: filterKeys.processing });
- expect(value).toEqual('light');
- value = hooks.getstatusBadgeVariant({ status: filterKeys.videoStatus });
- expect(value).toBeNull();
- value = hooks.getstatusBadgeVariant({ status: filterKeys.ready });
- expect(value).toBeNull();
- });
- });
- describe('videoListProps outputs', () => {
- const props = {
- searchSortProps: {
- searchString: 'Es',
- sortBy: sortKeys.dateNewest,
- filterBy: filterKeys.videoStatus,
- },
- videos: [
- {
- displayName: 'sOmEuiMAge',
- staTICUrl: '/assets/sOmEuiMAge',
- id: 'sOmEuiMAgeURl',
- },
- ],
- };
- const filterList = (args) => ({ filterList: args });
- const load = () => {
- jest.spyOn(hooks, hookKeys.filterList).mockImplementationOnce(filterList);
- hook = hooks.videoListProps(props);
- };
- beforeEach(() => {
- load();
- });
- describe('selectBtnProps', () => {
- test('on click, if sets selection', () => {
- const highlighted = 'videoId';
- state.mockVal(state.keys.highlighted, highlighted);
- load();
- expect(appHooks.navigateTo).not.toHaveBeenCalled();
- hook.selectBtnProps.onClick();
- expect(appHooks.navigateTo).toHaveBeenCalled();
- });
- test('on click, sets showSelectVideoError to true if nothing is highlighted', () => {
- state.mockVal(state.keys.highlighted, null);
- load();
- hook.selectBtnProps.onClick();
- expect(appHooks.navigateTo).not.toHaveBeenCalled();
- expect(state.setState.showSelectVideoError).toHaveBeenCalledWith(true);
- });
- });
- describe('galleryProps', () => {
- it('returns highlighted value, initialized to null', () => {
- expect(hook.galleryProps.highlighted).toEqual(state.stateVals.highlighted);
- expect(state.stateVals.highlighted).toEqual(null);
- });
- test('onHighlightChange sets highlighted with event target value', () => {
- hook.galleryProps.onHighlightChange({ target: { value: testValue } });
- expect(state.setState.highlighted).toHaveBeenCalledWith(testValue);
- });
- test('displayList returns filterListhook called with searchSortProps and videos', () => {
- expect(hook.galleryProps.displayList).toEqual(filterList({
- ...props.searchSortProps,
- videos: props.videos,
- }));
- });
- });
- describe('galleryError', () => {
- test('show is initialized to false and returns properly', () => {
- const show = 'sHOWSelectiRROr';
- expect(hook.galleryError.show).toEqual(false);
- state.mockVal(state.keys.showSelectVideoError, show);
- hook = hooks.videoListProps(props);
- expect(hook.galleryError.show).toEqual(show);
- });
- test('set sets showSelectVideoError to true', () => {
- hook.galleryError.set();
- expect(state.setState.showSelectVideoError).toHaveBeenCalledWith(true);
- });
- test('dismiss sets showSelectVideoError to false', () => {
- hook.galleryError.dismiss();
- expect(state.setState.showSelectVideoError).toHaveBeenCalledWith(false);
- });
- });
- });
- });
- describe('fileInputHooks', () => {
- test('click calls current.click on the ref', () => {
- jest.spyOn(hooks, hookKeys.handleVideoUpload).mockImplementationOnce();
- expect(hooks.handleVideoUpload).not.toHaveBeenCalled();
- hook = hooks.fileInputProps();
- expect(hooks.handleVideoUpload).toHaveBeenCalled();
- expect(appHooks.navigateTo).not.toHaveBeenCalled();
- hook.click();
- expect(appHooks.navigateTo).toHaveBeenCalled();
- });
- });
- describe('videoProps', () => {
- const videoList = {
- galleryProps: 'some gallery props',
- selectBtnProps: 'some select btn props',
- };
- const searchAndSortProps = { search: 'props' };
- const fileInput = { file: 'input hooks' };
- const videos = { video: { staTICUrl: '/assets/sOmEuiMAge' } };
- const spies = {};
- beforeEach(() => {
- spies.videoList = jest.spyOn(hooks, hookKeys.videoListProps)
- .mockReturnValueOnce(videoList);
- spies.search = jest.spyOn(hooks, hookKeys.searchAndSortProps)
- .mockReturnValueOnce(searchAndSortProps);
- spies.file = jest.spyOn(hooks, hookKeys.fileInputProps)
- .mockReturnValueOnce(fileInput);
- hook = hooks.videoProps({ videos });
- });
- it('forwards fileInput as fileInput', () => {
- expect(hook.fileInput).toEqual(fileInput);
- expect(spies.file.mock.calls.length).toEqual(1);
- expect(spies.file).toHaveBeenCalled();
- });
- it('initializes videoList', () => {
- expect(spies.videoList.mock.calls.length).toEqual(1);
- expect(spies.videoList).toHaveBeenCalledWith({
- searchSortProps: searchAndSortProps,
- videos,
- });
- });
- it('forwards searchAndSortHooks as searchSortProps', () => {
- expect(hook.searchSortProps).toEqual(searchAndSortProps);
- expect(spies.search.mock.calls.length).toEqual(1);
- expect(spies.search).toHaveBeenCalled();
- });
- it('forwards galleryProps and selectBtnProps from the video list hooks', () => {
- expect(hook.galleryProps).toEqual(videoList.galleryProps);
- expect(hook.selectBtnProps).toEqual(videoList.selectBtnProps);
- });
- });
- describe('handleCancel', () => {
- it('calls navigateCallback', () => {
- expect(hooks.handleCancel()).toEqual(
- appHooks.navigateCallback({
- destination: reactRedux.useSelector(selectors.app.returnUrl),
- analyticsEvent: analyticsEvt.videoGalleryCancelClick,
- analytics: reactRedux.useSelector(selectors.app.analytics),
- }),
- );
- });
- });
-});
diff --git a/src/editors/containers/VideoGallery/index.jsx b/src/editors/containers/VideoGallery/index.jsx
index 462a7371b..0600c1334 100644
--- a/src/editors/containers/VideoGallery/index.jsx
+++ b/src/editors/containers/VideoGallery/index.jsx
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
+import { useSelector } from 'react-redux';
import { selectors } from '../../data/redux';
import hooks from './hooks';
import SelectionModal from '../../sharedComponents/SelectionModal';
@@ -8,15 +7,19 @@ import { acceptedImgKeys } from './utils';
import messages from './messages';
import { RequestKeys } from '../../data/constants/requests';
-export const VideoGallery = ({
- // redux
- rawVideos,
- isLoaded,
- isFetchError,
- isUploadError,
-}) => {
+export const VideoGallery = () => {
+ const rawVideos = useSelector(selectors.app.videos);
+ const isLoaded = useSelector(
+ (state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
+ );
+ const isFetchError = useSelector(
+ (state) => selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchVideos }),
+ );
+ const isUploadError = useSelector(
+ (state) => selectors.requests.isFailed(state, { requestKey: RequestKeys.uploadVideo }),
+ );
const videos = hooks.buildVideos({ rawVideos });
- const handleVideoUpload = hooks.handleVideoUpload();
+ const handleVideoUpload = hooks.useVideoUploadHandler();
useEffect(() => {
// If no videos exists redirects to the video upload screen
@@ -31,8 +34,8 @@ export const VideoGallery = ({
galleryProps,
searchSortProps,
selectBtnProps,
- } = hooks.videoProps({ videos });
- const handleCancel = hooks.handleCancel();
+ } = hooks.useVideoProps({ videos });
+ const handleCancel = hooks.useCancelHandler();
const modalMessages = {
confirmMsg: messages.selectVideoButtonlabel,
@@ -43,44 +46,28 @@ export const VideoGallery = ({
};
return (
-
-
-
+
);
};
-VideoGallery.propTypes = {
- rawVideos: PropTypes.shape({}).isRequired,
- isLoaded: PropTypes.bool.isRequired,
- isFetchError: PropTypes.bool.isRequired,
- isUploadError: PropTypes.bool.isRequired,
-};
+VideoGallery.propTypes = {};
-export const mapStateToProps = (state) => ({
- rawVideos: selectors.app.videos(state),
- isLoaded: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
- isFetchError: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchVideos }),
- isUploadError: selectors.requests.isFailed(state, { requestKey: RequestKeys.uploadVideo }),
-});
-
-export const mapDispatchToProps = {};
-
-export default connect(mapStateToProps, mapDispatchToProps)(VideoGallery);
+export default VideoGallery;
diff --git a/src/editors/containers/VideoGallery/index.test.jsx b/src/editors/containers/VideoGallery/index.test.jsx
index ccbab262b..1ac21014d 100644
--- a/src/editors/containers/VideoGallery/index.test.jsx
+++ b/src/editors/containers/VideoGallery/index.test.jsx
@@ -1,104 +1,192 @@
+import { initializeMockApp } from '@edx/frontend-platform';
+import { AppProvider } from '@edx/frontend-platform/react';
+import { configureStore } from '@reduxjs/toolkit';
+import '@testing-library/jest-dom/extend-expect';
import React from 'react';
-import { shallow, mount } from 'enzyme';
+import {
+ act, fireEvent, render, screen,
+} from '@testing-library/react';
-import SelectionModal from '../../sharedComponents/SelectionModal';
-import hooks from './hooks';
-import * as module from '.';
+import { VideoGallery } from './index';
-jest.mock('../../sharedComponents/SelectionModal', () => 'SelectionModal');
+jest.unmock('react-redux');
+jest.unmock('@edx/frontend-platform/i18n');
+jest.unmock('@edx/paragon');
+jest.unmock('@edx/paragon/icons');
-const mockHandleVideoUploadHook = jest.fn();
-
-jest.mock('./hooks', () => ({
- buildVideos: jest.fn(() => []),
- videoProps: jest.fn(() => ({
- galleryError: {
- show: 'ShoWERror gAlLery',
- set: jest.fn(),
- dismiss: jest.fn(),
- message: {
- id: 'Gallery error id',
- defaultMessage: 'Gallery error',
- description: 'Gallery error',
- },
- },
- inputError: {
- show: 'ShoWERror inPUT',
- set: jest.fn(),
- dismiss: jest.fn(),
- message: {
- id: 'Input error id',
- defaultMessage: 'Input error',
- description: 'Input error',
- },
- },
- fileInput: {
- addFile: 'videoHooks.fileInput.addFile',
- click: 'videoHooks.fileInput.click',
- ref: 'videoHooks.fileInput.ref',
- },
- galleryProps: { gallery: 'props' },
- searchSortProps: { search: 'sortProps' },
- selectBtnProps: { select: 'btnProps' },
- })),
- handleCancel: jest.fn(),
- handleVideoUpload: () => mockHandleVideoUploadHook,
-}));
-
-jest.mock('../../data/redux', () => ({
- selectors: {
- requests: {
- isLoaded: (state, { requestKey }) => ({ isLoaded: { state, requestKey } }),
- isFetchError: (state, { requestKey }) => ({ isFetchError: { state, requestKey } }),
- isUploadError: (state, { requestKey }) => ({ isUploadError: { state, requestKey } }),
- },
+let store;
+const initialVideos = [
+ {
+ edx_video_id: 'id_1',
+ client_video_id: 'client_id_1',
+ course_video_image_url: 'course_video_image_url_1',
+ created: '2022-09-07T04:56:58.726Z',
+ status: 'Uploading',
+ duration: 3,
+ transcripts: [],
},
-}));
+ {
+ edx_video_id: 'id_2',
+ client_video_id: 'client_id_2',
+ course_video_image_url: 'course_video_image_url_2',
+ created: '2022-11-07T04:56:58.726Z',
+ status: 'In Progress',
+ duration: 2,
+ transcripts: [],
+ }, {
+ edx_video_id: 'id_3',
+ client_video_id: 'client_id_3',
+ course_video_image_url: 'course_video_image_url_3',
+ created: '2022-01-07T04:56:58.726Z',
+ status: 'Ready',
+ duration: 4,
+ transcripts: [],
+ },
+];
-jest.mock('../../hooks', () => ({
- ...jest.requireActual('../../hooks'),
- navigateCallback: jest.fn((args) => ({ navigateCallback: args })),
-}));
+// We are not using any style-based assertions and this function is very slow with jest-dom
+window.getComputedStyle = () => ({
+ getPropertyValue: () => undefined,
+});
describe('VideoGallery', () => {
describe('component', () => {
- const props = {
- rawVideos: { sOmEaSsET: { staTICUrl: '/video/sOmEaSsET' } },
- isLoaded: false,
- isFetchError: false,
- isUploadError: false,
- };
- let el;
- const videoProps = hooks.videoProps();
- beforeEach(() => {
- el = shallow();
- mockHandleVideoUploadHook.mockReset();
+ let oldLocation;
+ beforeEach(async () => {
+ store = configureStore({
+ reducer: (state, action) => ((action && action.newState) ? action.newState : state),
+ preloadedState: {
+ app: {
+ videos: initialVideos,
+ learningContextId: 'course-v1:test+test+test',
+ blockId: 'some-block-id',
+ },
+ requests: {
+ fetchVideos: { status: 'completed' },
+ uploadVideo: { status: 'inactive' },
+ },
+ },
+ });
+
+ initializeMockApp({
+ authenticatedUser: {
+ userId: 3,
+ username: 'test-user',
+ administrator: true,
+ roles: [],
+ },
+ });
});
- it('provides confirm action, forwarding selectBtnProps from imgHooks', () => {
- expect(el.find(SelectionModal).props().selectBtnProps).toEqual(
- expect.objectContaining({ ...hooks.videoProps().selectBtnProps }),
+ beforeAll(() => {
+ oldLocation = window.location;
+ delete window.location;
+ window.location = { assign: jest.fn() };
+ });
+ afterAll(() => {
+ window.location = oldLocation;
+ });
+
+ function updateState({ videos = initialVideos, fetchVideos = 'completed', uploadVideos = 'inactive' }) {
+ store.dispatch({
+ type: '',
+ newState: {
+ app: {
+ videos,
+ learningContextId: 'course-v1:test+test+test',
+ blockId: 'some-block-id',
+ },
+ requests: {
+ fetchVideos: { status: fetchVideos },
+ uploadVideo: { status: uploadVideos },
+ },
+ },
+ });
+ }
+
+ async function renderComponent() {
+ return render(
+
+
+ ,
);
+ }
+
+ it('displays a list of videos', async () => {
+ await renderComponent();
+ initialVideos.forEach(video => (
+ expect(screen.getByText(video.client_video_id)).toBeInTheDocument()
+ ));
});
- it('provides file upload button linked to fileInput.click', () => {
- expect(el.find(SelectionModal).props().fileInput.click).toEqual(
- videoProps.fileInput.click,
- );
+ it('navigates to video upload page when there are no videos', async () => {
+ expect(window.location.assign).not.toHaveBeenCalled();
+ updateState({ videos: [] });
+ await renderComponent();
+ expect(window.location.assign).toHaveBeenCalled();
});
- it('provides a SearchSort component with searchSortProps from imgHooks', () => {
- expect(el.find(SelectionModal).props().searchSortProps).toEqual(videoProps.searchSortProps);
+ it.each([
+ [/by date added \(newest\)/i, [2, 1, 3]],
+ [/by date added \(oldest\)/i, [3, 1, 2]],
+ [/by name \(ascending\)/i, [1, 2, 3]],
+ [/by name \(descending\)/i, [3, 2, 1]],
+ [/by duration \(longest\)/i, [3, 1, 2]],
+ [/by duration \(shortest\)/i, [2, 1, 3]],
+ ])('videos can be sorted %s', async (sortBy, order) => {
+ await renderComponent();
+
+ fireEvent.click(screen.getByRole('button', {
+ name: /by date added \(newest\)/i,
+ }));
+ fireEvent.click(screen.getByRole('link', {
+ name: sortBy,
+ }));
+ const videoElements = screen.getAllByRole('button', { name: /client_id/ });
+ order.forEach((clientIdSuffix, idx) => {
+ expect(videoElements[idx]).toHaveTextContent(`client_id_${clientIdSuffix}`);
+ });
});
- it('provides a Gallery component with galleryProps from imgHooks', () => {
- expect(el.find(SelectionModal).props().galleryProps).toEqual(videoProps.galleryProps);
+ it.each([
+ ['Uploading', 1, [1]],
+ ['Processing', 1, [2]],
+ ['Ready', 1, [3]],
+ ['Failed', 1, [4]],
+ ])('videos can be filtered by status %s', async (filterBy, length, items) => {
+ await renderComponent();
+ updateState({
+ videos: [...initialVideos, {
+ edx_video_id: 'id_4',
+ client_video_id: 'client_id_4',
+ course_video_image_url: 'course_video_image_url_4',
+ created: '2022-01-07T04:56:58.726Z',
+ status: 'Failed',
+ duration: 4,
+ transcripts: [],
+ }],
+ });
+
+ await act(async () => {
+ fireEvent.click(screen.getByRole('button', {
+ name: 'Video status',
+ }));
+ });
+ await act(async () => {
+ fireEvent.click(screen.getByRole('checkbox', {
+ name: filterBy,
+ }));
+ });
+
+ const videoElements = await screen.findAllByRole('button', { name: /client_id/ });
+ expect(videoElements).toHaveLength(length);
+ items.forEach(clientIdx => (
+ expect(screen.getByText(`client_id_${clientIdx}`)).toBeInTheDocument()
+ ));
});
- it('provides a FileInput component with fileInput props from imgHooks', () => {
- expect(el.find(SelectionModal).props().fileInput).toMatchObject(videoProps.fileInput);
- });
- it('handleVideoUpload called if there are no videos', () => {
- el = mount();
- expect(mockHandleVideoUploadHook).not.toHaveBeenCalled();
- el.setProps({ rawVideos: {}, isLoaded: true });
- el.mount();
- expect(mockHandleVideoUploadHook).toHaveBeenCalled();
+
+ it('filters videos by search string', async () => {
+ await renderComponent();
+ fireEvent.change(screen.getByRole('textbox'), { target: { value: 'CLIENT_ID_2' } });
+ expect(screen.queryByText('client_id_2')).toBeInTheDocument();
+ expect(screen.queryByText('client_id_1')).not.toBeInTheDocument();
+ expect(screen.queryByText('client_id_3')).not.toBeInTheDocument();
});
});
});
diff --git a/src/editors/containers/VideoGallery/utils.js b/src/editors/containers/VideoGallery/utils.js
index 989a84e80..8a1373948 100644
--- a/src/editors/containers/VideoGallery/utils.js
+++ b/src/editors/containers/VideoGallery/utils.js
@@ -22,15 +22,14 @@ export const sortMessages = StrictDict({
});
export const filterKeys = StrictDict({
- videoStatus: 'videoStatus',
- uploading: 'uploading',
- processing: 'processing',
- ready: 'ready',
- failed: 'failed',
+ uploading: 'Uploading',
+ processing: 'In Progress',
+ ready: 'Ready',
+ failed: 'Failed',
});
export const filterMessages = StrictDict({
- videoStatus: messages[messageKeys.filterByVideoStatusNone],
+ title: messages[messageKeys.filterByVideoStatusNone],
uploading: messages[messageKeys.filterByVideoStatusUploading],
processing: messages[messageKeys.filterByVideoStatusProcessing],
ready: messages[messageKeys.filterByVideoStatusReady],
diff --git a/src/editors/sharedComponents/BaseModal/__snapshots__/index.test.jsx.snap b/src/editors/sharedComponents/BaseModal/__snapshots__/index.test.jsx.snap
index aee237271..449a753f4 100644
--- a/src/editors/sharedComponents/BaseModal/__snapshots__/index.test.jsx.snap
+++ b/src/editors/sharedComponents/BaseModal/__snapshots__/index.test.jsx.snap
@@ -8,6 +8,7 @@ exports[`BaseModal ImageUploadModal template component snapshot 1`] = `
isOpen={true}
onClose={[MockFunction props.close]}
size="lg"
+ title="props.title node"
variant="default"
>
diff --git a/src/editors/sharedComponents/SelectionModal/Gallery.jsx b/src/editors/sharedComponents/SelectionModal/Gallery.jsx
index 8a543bb94..37e61e990 100644
--- a/src/editors/sharedComponents/SelectionModal/Gallery.jsx
+++ b/src/editors/sharedComponents/SelectionModal/Gallery.jsx
@@ -7,15 +7,13 @@ import {
import {
FormattedMessage,
- injectIntl,
- intlShape,
+ useIntl,
} from '@edx/frontend-platform/i18n';
import messages from './messages';
import GalleryCard from './GalleryCard';
export const Gallery = ({
- show,
galleryIsEmpty,
searchIsEmpty,
displayList,
@@ -25,12 +23,8 @@ export const Gallery = ({
showIdsOnCards,
height,
isLoaded,
- // injected
- intl,
}) => {
- if (!show) {
- return null;
- }
+ const intl = useIntl();
if (!isLoaded) {
return (
({
@@ -18,28 +19,28 @@ describe('TextEditor Image Gallery component', () => {
describe('component', () => {
const props = {
galleryIsEmpty: false,
+ emptyGalleryLabel: {
+ id: 'emptyGalleryMsg',
+ defaultMessage: 'Empty Gallery',
+ },
searchIsEmpty: false,
displayList: [{ id: 1 }, { id: 2 }, { id: 3 }],
highlighted: 'props.highlighted',
onHighlightChange: jest.fn().mockName('props.onHighlightChange'),
- intl: { formatMessage },
isLoaded: true,
};
+ const shallowWithIntl = (component) => shallow(
{component});
test('snapshot: not loaded, show spinner', () => {
- expect(shallow(
)).toMatchSnapshot();
+ expect(shallowWithIntl(
)).toMatchSnapshot();
});
test('snapshot: loaded but no images, show empty gallery', () => {
- expect(shallow(
)).toMatchSnapshot();
+ expect(shallowWithIntl(
)).toMatchSnapshot();
});
test('snapshot: loaded but search returns no images, show 0 search result gallery', () => {
- expect(shallow(
)).toMatchSnapshot();
+ expect(shallowWithIntl(
)).toMatchSnapshot();
});
test('snapshot: loaded, show gallery', () => {
- expect(shallow(
)).toMatchSnapshot();
- });
- test('snapshot: not shot gallery', () => {
- const wrapper = shallow(
);
- expect(wrapper.type()).toBeNull();
+ expect(shallowWithIntl(
)).toMatchSnapshot();
});
});
});
diff --git a/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx b/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
index 85217ed1a..0a8c75353 100644
--- a/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
+++ b/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
@@ -16,23 +16,18 @@ export const GalleryCard = ({
asset,
}) => (
-
-
+
)}
-
+
{asset.displayName}
{ asset.transcripts && (
@@ -86,7 +81,7 @@ GalleryCard.propTypes = {
displayName: PropTypes.string,
externalUrl: PropTypes.string,
id: PropTypes.string,
- dateAdded: PropTypes.number,
+ dateAdded: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
locked: PropTypes.bool,
portableUrl: PropTypes.string,
thumbnail: PropTypes.string,
@@ -94,7 +89,7 @@ GalleryCard.propTypes = {
duration: PropTypes.number,
status: PropTypes.string,
statusBadgeVariant: PropTypes.string,
- transcripts: PropTypes.shape([]),
+ transcripts: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
};
diff --git a/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx b/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx
new file mode 100644
index 000000000..b6ec2fd6c
--- /dev/null
+++ b/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Dropdown, DropdownToggle, Form } from '@edx/paragon';
+
+import PropTypes from 'prop-types';
+import { filterKeys, filterMessages } from '../../containers/VideoGallery/utils';
+
+const MultiSelectFilterDropdown = ({
+ selected, onSelectionChange,
+}) => {
+ const intl = useIntl();
+ return (
+
+
+ {intl.formatMessage(filterMessages.title)}
+
+
+ {Object.keys(filterKeys).map(key => (
+
+ {intl.formatMessage(filterMessages[key])}
+
+ ))}
+
+
+ );
+};
+
+MultiSelectFilterDropdown.propTypes = {
+ selected: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onSelectionChange: PropTypes.func.isRequired,
+};
+export default MultiSelectFilterDropdown;
diff --git a/src/editors/sharedComponents/SelectionModal/SearchSort.jsx b/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
index 762255877..d9b00e554 100644
--- a/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
+++ b/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
@@ -2,16 +2,17 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
- ActionRow, Dropdown, Form, Icon, IconButton,
+ ActionRow, Form, Icon, IconButton, SelectMenu, MenuItem,
} from '@edx/paragon';
import { Close, Search } from '@edx/paragon/icons';
import {
FormattedMessage,
- injectIntl,
- intlShape,
+ useIntl,
} from '@edx/frontend-platform/i18n';
import messages from './messages';
+import MultiSelectFilterDropdown from './MultiSelectFilterDropdown';
+import { sortKeys, sortMessages } from '../../containers/VideoGallery/utils';
export const SearchSort = ({
searchString,
@@ -19,28 +20,25 @@ export const SearchSort = ({
clearSearchString,
sortBy,
onSortClick,
- sortKeys,
- sortMessages,
filterBy,
onFilterClick,
- filterKeys,
- filterMessages,
showSwitch,
switchMessage,
onSwitchClick,
- // injected
- intl,
-}) => (
-
-
- {
+ const intl = useIntl();
+ return (
+
+
+
}
- value={searchString}
- />
-
+ value={searchString}
+ />
+
- { !showSwitch && }
-
-
-
-
-
+ { !showSwitch && }
+
{Object.keys(sortKeys).map(key => (
-
+
+
))}
-
-
+
- { filterKeys && filterMessages && (
-
-
-
-
-
- {Object.keys(filterKeys).map(key => (
-
-
-
- ))}
-
-
- )}
+ {onFilterClick && }
- { showSwitch && (
- <>
-
-
-
-
-
-
- >
- )}
+ { showSwitch && (
+ <>
+
+
+
+
+
+
+ >
+ )}
-
-);
+
+ );
+};
SearchSort.defaultProps = {
filterBy: '',
onFilterClick: null,
- filterKeys: null,
- filterMessages: null,
showSwitch: false,
onSwitchClick: null,
};
@@ -117,17 +96,11 @@ SearchSort.propTypes = {
clearSearchString: PropTypes.func.isRequired,
sortBy: PropTypes.string.isRequired,
onSortClick: PropTypes.func.isRequired,
- sortKeys: PropTypes.shape({}).isRequired,
- sortMessages: PropTypes.shape({}).isRequired,
- filterBy: PropTypes.string,
+ filterBy: PropTypes.arrayOf(PropTypes.string),
onFilterClick: PropTypes.func,
- filterKeys: PropTypes.shape({}),
- filterMessages: PropTypes.shape({}),
showSwitch: PropTypes.bool,
switchMessage: PropTypes.shape({}).isRequired,
onSwitchClick: PropTypes.func,
- // injected
- intl: intlShape.isRequired,
};
-export default injectIntl(SearchSort);
+export default SearchSort;
diff --git a/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx b/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
index ccf8d36c8..74899d3a2 100644
--- a/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
+++ b/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
@@ -1,101 +1,89 @@
import React from 'react';
-import { shallow } from 'enzyme';
-import { Dropdown } from '@edx/paragon';
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import { formatMessage } from '../../../testUtils';
+import '@testing-library/jest-dom';
+import {
+ act, fireEvent, render, screen,
+} from '@testing-library/react';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { sortKeys, sortMessages } from '../ImageUploadModal/SelectImageModal/utils';
-import { filterKeys, filterMessages } from '../../containers/VideoGallery/utils';
+import { filterMessages } from '../../containers/VideoGallery/utils';
import { SearchSort } from './SearchSort';
+import messages from './messages';
+
+jest.unmock('react-redux');
+jest.unmock('@edx/frontend-platform/i18n');
+jest.unmock('@edx/paragon');
+jest.unmock('@edx/paragon/icons');
describe('SearchSort component', () => {
- describe('snapshots without filterKeys', () => {
- const props = {
- searchString: 'props.searchString',
- onSearchChange: jest.fn().mockName('props.onSearchChange'),
- clearSearchString: jest.fn().mockName('props.clearSearchString'),
- sortBy: sortKeys.dateOldest,
- sortKeys,
- sortMessages,
- onSortClick: jest.fn().mockName('props.onSortClick'),
- intl: { formatMessage },
- };
- test('with search string (close button)', () => {
- expect(shallow(
)).toMatchSnapshot();
- });
- test('without search string (search icon)', () => {
- expect(shallow(
)).toMatchSnapshot();
- });
- test('adds a sort option for each sortKey', () => {
- const el = shallow(
);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
+ const props = {
+ searchString: '',
+ onSearchChange: jest.fn()
+ .mockName('props.onSearchChange'),
+ clearSearchString: jest.fn()
+ .mockName('props.clearSearchString'),
+ sortBy: sortKeys.dateOldest,
+ sortKeys,
+ sortMessages,
+ onSortClick: jest.fn()
+ .mockName('props.onSortClick'),
+ switchMessage: {
+ id: 'test.id',
+ defaultMessage: 'test message',
+ },
+ onFilterClick: jest.fn(),
+ showSwitch: true,
+ };
+
+ function getComponent(overrideProps = {}) {
+ return render(
+
+
+ ,
+ );
+ }
+
+ test('adds a sort option for each sortKey', async () => {
+ const { getByRole } = getComponent();
+ await act(() => {
+ fireEvent.click(screen.getByRole('button', {
+ name: /by date added \(oldest\)/i,
+ }));
});
+ Object.values(sortMessages)
+ .forEach(({ defaultMessage }) => {
+ expect(getByRole('link', { name: defaultMessage }))
+ .toBeInTheDocument();
+ });
});
- describe('snapshots with filterKeys', () => {
- const props = {
- searchString: 'props.searchString',
- onSearchChange: jest.fn().mockName('props.onSearchChange'),
- clearSearchString: jest.fn().mockName('props.clearSearchString'),
- sortBy: sortKeys.dateOldest,
- sortKeys,
- sortMessages,
- filterKeys,
- filterMessages,
- showSwitch: true,
- onSortClick: jest.fn().mockName('props.onSortClick'),
- onFilterClick: jest.fn().mockName('props.onFilterClick'),
- intl: { formatMessage },
- };
- test('with search string (close button)', () => {
- expect(shallow(
)).toMatchSnapshot();
+ test('adds a sort option for each sortKey', async () => {
+ const { getByRole } = getComponent();
+ await act(() => {
+ fireEvent.click(screen.getByRole('button', { name: /by date added \(oldest\)/i }));
});
- test('without search string (search icon)', () => {
- expect(shallow(
)).toMatchSnapshot();
- });
- test('adds a sort option for each sortKey', () => {
- const el = shallow(
);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- });
- test('adds a filter option for each filterKet', () => {
- const el = shallow(
);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
- expect(el.find(Dropdown).containsMatchingElement(
-
,
- )).toEqual(true);
+ Object.values(sortMessages)
+ .forEach(({ defaultMessage }) => {
+ expect(getByRole('link', { name: defaultMessage }))
+ .toBeInTheDocument();
+ });
+ });
+ test('adds a filter option for each filterKet', async () => {
+ const { getByRole } = getComponent();
+ await act(() => {
+ fireEvent.click(screen.getByRole('button', { name: /video status/i }));
});
+ Object.keys(filterMessages)
+ .forEach((key) => {
+ if (key !== 'title') {
+ expect(getByRole('checkbox', { name: filterMessages[key].defaultMessage }))
+ .toBeInTheDocument();
+ }
+ });
+ });
+ test('searchbox should show clear message button when not empty', async () => {
+ const { queryByRole } = getComponent({ searchString: 'some string' });
+ expect(queryByRole('button', { name: messages.clearSearch.defaultMessage }))
+ .toBeInTheDocument();
});
});
diff --git a/src/editors/sharedComponents/SelectionModal/__snapshots__/Gallery.test.jsx.snap b/src/editors/sharedComponents/SelectionModal/__snapshots__/Gallery.test.jsx.snap
index 029cbbaf2..10cddbd47 100644
--- a/src/editors/sharedComponents/SelectionModal/__snapshots__/Gallery.test.jsx.snap
+++ b/src/editors/sharedComponents/SelectionModal/__snapshots__/Gallery.test.jsx.snap
@@ -1,104 +1,297 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TextEditor Image Gallery component component snapshot: loaded but no images, show empty gallery 1`] = `
-
-
-
+
+
`;
exports[`TextEditor Image Gallery component component snapshot: loaded but search returns no images, show 0 search result gallery 1`] = `
-
-
-
+
`;
exports[`TextEditor Image Gallery component component snapshot: loaded, show gallery 1`] = `
-
-
-
-
-
-
-
-
-
+
+
`;
exports[`TextEditor Image Gallery component component snapshot: not loaded, show spinner 1`] = `
-
-
-
+
`;
diff --git a/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap b/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
index 71c91147a..769e70144 100644
--- a/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
+++ b/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
@@ -2,26 +2,18 @@
exports[`GalleryCard component snapshot with duration badge 1`] = `
-
-
- }
- value="props.searchString"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`SearchSort component snapshots with filterKeys without search string (search icon) 1`] = `
-
-
- }
- value=""
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`SearchSort component snapshots without filterKeys with search string (close button) 1`] = `
-
-
-
- }
- value="props.searchString"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`SearchSort component snapshots without filterKeys without search string (search icon) 1`] = `
-
-
- }
- value=""
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/editors/sharedComponents/SelectionModal/index.jsx b/src/editors/sharedComponents/SelectionModal/index.jsx
index 9ddd77e90..d57c717ed 100644
--- a/src/editors/sharedComponents/SelectionModal/index.jsx
+++ b/src/editors/sharedComponents/SelectionModal/index.jsx
@@ -5,8 +5,7 @@ import { Button, Stack } from '@edx/paragon';
import { Add } from '@edx/paragon/icons';
import {
FormattedMessage,
- injectIntl,
- intlShape,
+ useIntl,
} from '@edx/frontend-platform/i18n';
import BaseModal from '../BaseModal';
@@ -33,9 +32,8 @@ export const SelectionModal = ({
isLoaded,
isFetchError,
isUploadError,
- // injected
- intl,
}) => {
+ const intl = useIntl();
const {
confirmMsg,
uploadButtonMsg,
@@ -54,7 +52,6 @@ export const SelectionModal = ({
const galleryPropsValues = {
isLoaded,
- show: showGallery,
...galleryProps,
};
return (
@@ -109,7 +106,7 @@ export const SelectionModal = ({
-
+ {showGallery && }
@@ -155,8 +152,6 @@ SelectionModal.propTypes = {
isLoaded: PropTypes.bool.isRequired,
isFetchError: PropTypes.bool.isRequired,
isUploadError: PropTypes.bool.isRequired,
- // injected
- intl: intlShape.isRequired,
};
-export default injectIntl(SelectionModal);
+export default SelectionModal;
diff --git a/src/editors/sharedComponents/SelectionModal/index.test.jsx b/src/editors/sharedComponents/SelectionModal/index.test.jsx
index 45a29f3f8..6f716c3ee 100644
--- a/src/editors/sharedComponents/SelectionModal/index.test.jsx
+++ b/src/editors/sharedComponents/SelectionModal/index.test.jsx
@@ -1,13 +1,14 @@
import React from 'react';
+
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react';
-import { formatMessage } from '../../../testUtils';
+
import SelectionModal from '.';
import '@testing-library/jest-dom';
const props = {
- isOpen: jest.fn(),
- isClose: jest.fn(),
+ isOpen: true,
+ close: jest.fn(),
size: 'fullscreen',
isFullscreenScroll: false,
galleryError: {
@@ -35,7 +36,13 @@ const props = {
click: 'imgHooks.fileInput.click',
ref: 'imgHooks.fileInput.ref',
},
- galleryProps: { gallery: 'props' },
+ galleryProps: {
+ gallery: 'props',
+ emptyGalleryLabel: {
+ id: 'emptyGalleryMsg',
+ defaultMessage: 'Empty Gallery',
+ },
+ },
searchSortProps: { search: 'sortProps' },
selectBtnProps: { select: 'btnProps' },
acceptedFiles: { png: '.png' },
@@ -69,7 +76,6 @@ const props = {
isLoaded: true,
isFetchError: false,
isUploadError: false,
- intl: { formatMessage },
};
const mockGalleryFn = jest.fn();
@@ -105,7 +111,7 @@ describe('Selection Modal', () => {
});
test('rendering correctly with expected Input', async () => {
render(
-
+
,
);
@@ -118,7 +124,6 @@ describe('Selection Modal', () => {
expect.objectContaining({
...props.galleryProps,
isLoaded: props.isLoaded,
- show: true,
}),
);
expect(mockFetchErrorAlertFn).toHaveBeenCalledWith(
@@ -142,11 +147,11 @@ describe('Selection Modal', () => {
});
test('rendering correctly with errors', () => {
render(
-
+
,
);
- expect(screen.getByText('Gallery')).toBeInTheDocument();
+ expect(screen.queryByText('Gallery')).not.toBeInTheDocument();
expect(screen.getByText('FileInput')).toBeInTheDocument();
expect(screen.getByText('FetchErrorAlert')).toBeInTheDocument();
expect(screen.getByText('UploadErrorAlert')).toBeInTheDocument();
@@ -157,17 +162,10 @@ describe('Selection Modal', () => {
message: props.modalMessages.fetchError,
}),
);
- expect(mockGalleryFn).toHaveBeenCalledWith(
- expect.objectContaining({
- ...props.galleryProps,
- isLoaded: props.isLoaded,
- show: false,
- }),
- );
});
test('rendering correctly with loading', () => {
render(
-
+
,
);
@@ -180,7 +178,6 @@ describe('Selection Modal', () => {
expect.objectContaining({
...props.galleryProps,
isLoaded: false,
- show: true,
}),
);
});
diff --git a/src/editors/sharedComponents/SelectionModal/messages.js b/src/editors/sharedComponents/SelectionModal/messages.js
index 10d18d0c2..2aa74f9e2 100644
--- a/src/editors/sharedComponents/SelectionModal/messages.js
+++ b/src/editors/sharedComponents/SelectionModal/messages.js
@@ -4,6 +4,11 @@ export const messages = {
defaultMessage: 'Search',
description: 'Placeholder text for search bar',
},
+ clearSearch: {
+ id: 'authoring.selectionmodal.search.clearSearchButton',
+ defaultMessage: 'Clear search query',
+ description: 'Button to clear search query',
+ },
emptySearchLabel: {
id: 'authoring.selectionmodal.emptySearchLabel',
defaultMessage: 'No search results.',