diff --git a/src/editors/containers/VideoGallery/hooks.js b/src/editors/containers/VideoGallery/hooks.js index dde1fe7df..835c313a7 100644 --- a/src/editors/containers/VideoGallery/hooks.js +++ b/src/editors/containers/VideoGallery/hooks.js @@ -6,6 +6,7 @@ import { filterMessages, sortKeys, sortMessages, + sortFunctions, } from './utils'; export const state = { @@ -43,7 +44,43 @@ export const searchAndSortHooks = () => { }; }; -export const videoListHooks = ({ videos }) => { +export const filterListBySearch = ({ searchString, videoList }) => ( + videoList.filter(({ displayName }) => displayName.toLowerCase().includes(searchString.toLowerCase())) +); + +export const filterListByStatus = ({ statusFilter, videoList }) => { + if (statusFilter === filterKeys.videoStatus) { + return videoList; + } + return videoList.filter(({ status }) => status === statusFilter); +}; + +export const filterListByHideSelectedCourse = ({ videoList }) => ( + // TODO Missing to implement this + videoList +); + +export const filterList = ({ + sortBy, + filterBy, + searchString, + videos, +}) => { + let filteredList = module.filterListBySearch({ + searchString, + videoList: videos, + }); + filteredList = module.filterListByStatus({ + statusFilter: filterBy, + videoList: filteredList, + }); + filteredList = module.filterListByHideSelectedCourse({ + videoList: filteredList, + }); + return filteredList.sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest]); +}; + +export const videoListHooks = ({ searchSortProps, videos }) => { const [highlighted, setHighlighted] = module.state.highlighted(null); const [ showSelectVideoError, @@ -53,7 +90,7 @@ export const videoListHooks = ({ videos }) => { showSizeError, setShowSizeError, ] = module.state.showSizeError(false); - const filteredList = videos; // TODO missing filters and sort + const filteredList = module.filterList({ ...searchSortProps, videos }); return { galleryError: { show: showSelectVideoError, @@ -118,7 +155,7 @@ export const buildVideos = ({ rawVideos }) => { export const videoHooks = ({ videos }) => { const searchSortProps = module.searchAndSortHooks(); - const videoList = module.videoListHooks({ videos }); + const videoList = module.videoListHooks({ searchSortProps, videos }); const { galleryError, galleryProps, diff --git a/src/editors/containers/VideoGallery/hooks.test.js b/src/editors/containers/VideoGallery/hooks.test.js new file mode 100644 index 000000000..8508404fa --- /dev/null +++ b/src/editors/containers/VideoGallery/hooks.test.js @@ -0,0 +1,134 @@ +import * as hooks from './hooks'; +import { filterKeys, sortKeys } from './utils'; +import { MockUseState } from '../../../testUtils'; +import { keyStore } from '../../utils'; + +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), + }; +}); + +const state = new MockUseState(hooks); +const hookKeys = keyStore(hooks); +let hook; +const testValue = 'testVALUEVALID'; + +describe('VideoGallery hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('using state', () => { + beforeEach(() => { state.mock(); }); + afterEach(() => { state.restore(); }); + + describe('searchAndSortHooks', () => { + beforeEach(() => { + hook = hooks.searchAndSortHooks(); + }); + 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('imgListHooks 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.videoListHooks(props); + }; + beforeEach(() => { + load(); + }); + 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.videoListHooks(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); + }); + }); + }); + }); +}); diff --git a/src/editors/containers/VideoGallery/utils.js b/src/editors/containers/VideoGallery/utils.js index eb137072d..989a84e80 100644 --- a/src/editors/containers/VideoGallery/utils.js +++ b/src/editors/containers/VideoGallery/utils.js @@ -37,6 +37,27 @@ export const filterMessages = StrictDict({ failed: messages[messageKeys.filterByVideoStatusFailed], }); +export const sortFunctions = StrictDict({ + dateNewest: (a, b) => b.dateAdded - a.dateAdded, + dateOldest: (a, b) => a.dateAdded - b.dateAdded, + nameAscending: (a, b) => { + const nameA = a.displayName.toLowerCase(); + const nameB = b.displayName.toLowerCase(); + if (nameA < nameB) { return -1; } + if (nameB < nameA) { return 1; } + return b.dateAdded - a.dateAdded; + }, + nameDescending: (a, b) => { + const nameA = a.displayName.toLowerCase(); + const nameB = b.displayName.toLowerCase(); + if (nameA < nameB) { return 1; } + if (nameB < nameA) { return -1; } + return b.dateAdded - a.dateAdded; + }, + durationShortest: (a, b) => a.duration - b.duration, + durationLongest: (a, b) => b.duration - a.duration, +}); + export const acceptedImgKeys = StrictDict({ mp4: '.mp4', });