Feat video source integration (#125)
* feat: video source integration Co-authored-by: KristinAoki <kaoki@2u.com>
This commit is contained in:
@@ -60,8 +60,6 @@ export const TitleHeader = ({
|
||||
TitleHeader.defaultProps = {};
|
||||
TitleHeader.propTypes = {
|
||||
isInitialized: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
setTitle: PropTypes.func.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VideoEditor snapshots renders as expected with default behavior 1`] = `
|
||||
<EditorContainer
|
||||
getContent={[Function]}
|
||||
onClose={[MockFunction props.onClose]}
|
||||
validateEntry={[MockFunction validateEntry]}
|
||||
<Component
|
||||
value="hooks.errorsHook.error"
|
||||
>
|
||||
<div
|
||||
className="video-editor"
|
||||
<EditorContainer
|
||||
getContent={[Function]}
|
||||
onClose={[MockFunction props.onClose]}
|
||||
validateEntry={[MockFunction validateEntry]}
|
||||
>
|
||||
<VideoEditorModal
|
||||
error="errORsHooKErroR"
|
||||
/>
|
||||
</div>
|
||||
</EditorContainer>
|
||||
<div
|
||||
className="video-editor"
|
||||
>
|
||||
<VideoEditorModal />
|
||||
</div>
|
||||
</EditorContainer>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
@@ -17,30 +17,20 @@ export const hooks = {
|
||||
|
||||
const VideoEditorModal = ({
|
||||
close,
|
||||
error,
|
||||
isOpen,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
module.hooks.initialize(dispatch);
|
||||
return (
|
||||
<VideoSettingsModal {...{ close, error, isOpen }} />
|
||||
<VideoSettingsModal {...{ close, isOpen }} />
|
||||
);
|
||||
// TODO: add logic to show SelectVideoModal if no selection
|
||||
};
|
||||
|
||||
VideoEditorModal.defaultProps = {
|
||||
error: {
|
||||
duration: {},
|
||||
handout: {},
|
||||
license: {},
|
||||
thumbnail: {},
|
||||
transcripts: {},
|
||||
videoSource: {},
|
||||
},
|
||||
};
|
||||
VideoEditorModal.propTypes = {
|
||||
close: PropTypes.func.isRequired,
|
||||
error: PropTypes.node,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
export default VideoEditorModal;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { Info } from '@edx/paragon/icons';
|
||||
|
||||
import messages from './components/messages';
|
||||
import { ErrorContext } from '../../hooks';
|
||||
import * as module from './ErrorSummary';
|
||||
|
||||
export const hasNoError = (error) => Object.keys(error[0]).length === 0;
|
||||
|
||||
export const showAlert = (errors) => !Object.values(errors).every(module.hasNoError);
|
||||
|
||||
export const ErrorSummary = () => {
|
||||
const errors = React.useContext(ErrorContext);
|
||||
return (
|
||||
<Alert
|
||||
icon={Info}
|
||||
show={module.showAlert(errors)}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage {...messages.validateErrorTitle} />
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage {...messages.validateErrorBody} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorSummary;
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import * as module from './ErrorSummary';
|
||||
|
||||
describe('ErrorSummary', () => {
|
||||
const errors = {
|
||||
widgetWithError: [{ err1: 'mSg', err2: 'msG2' }, jest.fn()],
|
||||
widgetWithNoError: [{}, jest.fn()],
|
||||
};
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
describe('render', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(React, 'useContext').mockReturnValueOnce({});
|
||||
});
|
||||
test('snapshots: renders as expected when there are no errors', () => {
|
||||
jest.spyOn(module, 'showAlert').mockReturnValue(false);
|
||||
expect(shallow(<module.ErrorSummary />)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshots: renders as expected when there are errors', () => {
|
||||
jest.spyOn(module, 'showAlert').mockReturnValue(true);
|
||||
expect(shallow(<module.ErrorSummary />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('hasNoError', () => {
|
||||
it('returns true', () => {
|
||||
expect(module.hasNoError(errors.widgetWithError)).toEqual(false);
|
||||
});
|
||||
it('returns false', () => {
|
||||
expect(module.hasNoError(errors.widgetWithNoError)).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('showAlert', () => {
|
||||
it('returns true', () => {
|
||||
jest.spyOn(module, 'hasNoError').mockReturnValue(false);
|
||||
expect(module.showAlert(errors)).toEqual(true);
|
||||
});
|
||||
it('returns false', () => {
|
||||
jest.spyOn(module, 'hasNoError').mockReturnValue(true);
|
||||
expect(module.showAlert(errors)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorSummary render snapshots: renders as expected when there are errors 1`] = `
|
||||
<Alert
|
||||
show={true}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage
|
||||
defaultMessage="We couldn't add your video."
|
||||
description="Title of validation error."
|
||||
id="authoring.videoeditor.validate.error.title"
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Please check your entries and try again."
|
||||
description="Body of validation error."
|
||||
id="authoring.videoeditor.validate.error.body"
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`ErrorSummary render snapshots: renders as expected when there are no errors 1`] = `
|
||||
<Alert
|
||||
show={false}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage
|
||||
defaultMessage="We couldn't add your video."
|
||||
description="Title of validation error."
|
||||
id="authoring.videoeditor.validate.error.title"
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Please check your entries and try again."
|
||||
description="Body of validation error."
|
||||
id="authoring.videoeditor.validate.error.body"
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { Info } from '@edx/paragon/icons';
|
||||
import messages from './messages';
|
||||
|
||||
export const ErrorSummary = ({
|
||||
error,
|
||||
}) => (
|
||||
<Alert
|
||||
icon={Info}
|
||||
show={!Object.values(error).every(val => Object.keys(val).length === 0)}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage {...messages.validateErrorTitle} />
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage {...messages.validateErrorBody} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
ErrorSummary.defaultProps = {
|
||||
error: {
|
||||
duration: {},
|
||||
handout: {},
|
||||
license: {},
|
||||
thumbnail: {},
|
||||
transcripts: {},
|
||||
videoSource: {},
|
||||
},
|
||||
};
|
||||
ErrorSummary.propTypes = {
|
||||
error: PropTypes.node,
|
||||
};
|
||||
|
||||
export default ErrorSummary;
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { ErrorSummary } from './ErrorSummary';
|
||||
|
||||
describe('ErrorSummary', () => {
|
||||
const props = {
|
||||
error: 'eRrOr',
|
||||
};
|
||||
describe('render', () => {
|
||||
test('snapshots: renders as expected', () => {
|
||||
expect(
|
||||
shallow(<ErrorSummary {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshot: renders ErrorAlert with delete error message 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="English"
|
||||
title="Transcript"
|
||||
>
|
||||
@@ -115,7 +115,7 @@ exports[`TranscriptWidget snapshots snapshot: renders ErrorAlert with delete err
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshot: renders ErrorAlert with upload error message 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="English"
|
||||
title="Transcript"
|
||||
>
|
||||
@@ -228,7 +228,7 @@ exports[`TranscriptWidget snapshots snapshot: renders ErrorAlert with upload err
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshots: renders as expected with allowTranscriptDownloads true 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="English"
|
||||
title="Transcript"
|
||||
>
|
||||
@@ -341,7 +341,7 @@ exports[`TranscriptWidget snapshots snapshots: renders as expected with allowTra
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshots: renders as expected with default props 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="None"
|
||||
title="Transcript"
|
||||
>
|
||||
@@ -412,7 +412,7 @@ exports[`TranscriptWidget snapshots snapshots: renders as expected with default
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshots: renders as expected with showTranscriptByDefault true 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="English"
|
||||
title="Transcript"
|
||||
>
|
||||
@@ -525,7 +525,7 @@ exports[`TranscriptWidget snapshots snapshots: renders as expected with showTran
|
||||
|
||||
exports[`TranscriptWidget snapshots snapshots: renders as expected with transcripts 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
isError={false}
|
||||
isError={true}
|
||||
subtitle="English"
|
||||
title="Transcript"
|
||||
>
|
||||
|
||||
@@ -2,14 +2,26 @@ import React from 'react';
|
||||
import { thunkActions, actions } from '../../../../../../data/redux';
|
||||
import * as module from './hooks';
|
||||
import { videoTranscriptLanguages } from '../../../../../../data/constants/video';
|
||||
import { ErrorContext } from '../../../../hooks';
|
||||
import messages from './messages';
|
||||
|
||||
export const state = {
|
||||
inDeleteConfirmation: (args) => React.useState(args),
|
||||
};
|
||||
|
||||
export const updateErrors = ({ isUploadError, isDeleteError }) => {
|
||||
const [error, setError] = React.useContext(ErrorContext).transcripts;
|
||||
if (isUploadError) {
|
||||
setError({ ...error, uploadError: messages.uploadTranscriptError.defaultMessage });
|
||||
}
|
||||
if (isDeleteError) {
|
||||
setError({ ...error, deleteError: messages.deleteTranscriptError.defaultMessage });
|
||||
}
|
||||
};
|
||||
|
||||
export const transcriptLanguages = (transcripts) => {
|
||||
const languages = [];
|
||||
if (Object.keys(transcripts).length > 0) {
|
||||
if (transcripts && Object.keys(transcripts).length > 0) {
|
||||
Object.keys(transcripts).forEach(transcript => {
|
||||
languages.push(videoTranscriptLanguages[transcript]);
|
||||
});
|
||||
|
||||
@@ -28,12 +28,12 @@ import ErrorAlert from '../../../../../../sharedComponents/ErrorAlerts/ErrorAler
|
||||
import CollapsibleFormWidget from '../CollapsibleFormWidget';
|
||||
|
||||
import TranscriptListItem from './TranscriptListItem';
|
||||
import { ErrorContext } from '../../../../hooks';
|
||||
|
||||
/**
|
||||
* Collapsible Form widget controlling video transcripts
|
||||
*/
|
||||
export const TranscriptWidget = ({
|
||||
error,
|
||||
// redux
|
||||
transcripts,
|
||||
allowTranscriptDownloads,
|
||||
@@ -42,6 +42,7 @@ export const TranscriptWidget = ({
|
||||
isUploadError,
|
||||
isDeleteError,
|
||||
}) => {
|
||||
const [error] = React.useContext(ErrorContext).transcripts;
|
||||
const languagesArr = hooks.transcriptLanguages(transcripts);
|
||||
const fileInput = hooks.fileInput({ onAddFile: hooks.addFileCallback({ dispatch: useDispatch() }) });
|
||||
const hasTranscripts = hooks.hasTranscripts(transcripts);
|
||||
@@ -124,10 +125,8 @@ export const TranscriptWidget = ({
|
||||
};
|
||||
|
||||
TranscriptWidget.defaultProps = {
|
||||
error: {},
|
||||
};
|
||||
TranscriptWidget.propTypes = {
|
||||
error: PropTypes.node,
|
||||
// redux
|
||||
transcripts: PropTypes.shape({}).isRequired,
|
||||
allowTranscriptDownloads: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -7,6 +7,11 @@ import { formatMessage } from '../../../../../../../testUtils';
|
||||
import { actions, selectors } from '../../../../../../data/redux';
|
||||
import { TranscriptWidget, mapStateToProps, mapDispatchToProps } from '.';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useContext: jest.fn(() => ({ transcripts: ['error.transcripts', jest.fn().mockName('error.setTranscripts')] })),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../../data/redux', () => ({
|
||||
actions: {
|
||||
video: {
|
||||
|
||||
@@ -28,7 +28,6 @@ import CollapsibleFormWidget from '../CollapsibleFormWidget';
|
||||
* Collapsible Form widget controlling video source as well as fallback sources
|
||||
*/
|
||||
export const VideoSourceWidget = ({
|
||||
// error,
|
||||
// injected
|
||||
intl,
|
||||
// redux
|
||||
@@ -128,10 +127,8 @@ export const VideoSourceWidget = ({
|
||||
);
|
||||
};
|
||||
VideoSourceWidget.defaultProps = {
|
||||
// error: {},
|
||||
};
|
||||
VideoSourceWidget.propTypes = {
|
||||
// error: PropTypes.node,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
// redux
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorSummary render snapshots: renders as expected 1`] = `
|
||||
<Alert
|
||||
show={true}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage
|
||||
defaultMessage="We couldn't add your video."
|
||||
description="Title of validation error."
|
||||
id="authoring.videoeditor.validate.error.title"
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Please check your entries and try again."
|
||||
description="Body of validation error."
|
||||
id="authoring.videoeditor.validate.error.body"
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { thunkActions } from '../../../../data/redux';
|
||||
// import VideoPreview from './components/VideoPreview';
|
||||
import ErrorSummary from './components/ErrorSummary';
|
||||
import ErrorSummary from './ErrorSummary';
|
||||
import DurationWidget from './components/DurationWidget';
|
||||
import HandoutWidget from './components/HandoutWidget';
|
||||
import LicenseWidget from './components/LicenseWidget';
|
||||
@@ -20,39 +19,23 @@ export const hooks = {
|
||||
},
|
||||
};
|
||||
|
||||
export const VideoSettingsModal = ({
|
||||
error,
|
||||
}) => (
|
||||
export const VideoSettingsModal = () => (
|
||||
<div className="video-settings-modal row">
|
||||
<div className="video-preview col col-4">
|
||||
Video Preview goes here
|
||||
{/* <VideoPreview /> */}
|
||||
</div>
|
||||
<div className="video-controls col col-8">
|
||||
<ErrorSummary {...{ error }} />
|
||||
<ErrorSummary />
|
||||
<h3>Settings</h3>
|
||||
<VideoSourceWidget error={error.videoSource} />
|
||||
<ThumbnailWidget error={error.thumbnail} />
|
||||
<TranscriptWidget error={error.transcripts} />
|
||||
<DurationWidget error={error.duration} />
|
||||
<HandoutWidget error={error.handout} />
|
||||
<LicenseWidget error={error.license} />
|
||||
<VideoSourceWidget />
|
||||
<ThumbnailWidget />
|
||||
<TranscriptWidget />
|
||||
<DurationWidget />
|
||||
<HandoutWidget />
|
||||
<LicenseWidget />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
VideoSettingsModal.defaultProps = {
|
||||
error: {
|
||||
duration: {},
|
||||
handout: {},
|
||||
license: {},
|
||||
thumbnail: {},
|
||||
transcripts: {},
|
||||
videoSource: {},
|
||||
},
|
||||
};
|
||||
VideoSettingsModal.propTypes = {
|
||||
error: PropTypes.node,
|
||||
};
|
||||
|
||||
export default VideoSettingsModal;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, createContext } from 'react';
|
||||
|
||||
import { StrictDict } from '../../utils';
|
||||
import * as module from './hooks';
|
||||
|
||||
export const ErrorContext = createContext();
|
||||
|
||||
export const state = StrictDict({
|
||||
durationErrors: (val) => useState(val),
|
||||
handoutErrors: (val) => useState(val),
|
||||
@@ -22,59 +24,21 @@ export const errorsHook = () => {
|
||||
|
||||
return {
|
||||
error: {
|
||||
duration: durationErrors,
|
||||
handout: handoutErrors,
|
||||
license: licenseErrors,
|
||||
thumbnail: thumbnailErrors,
|
||||
transcripts: transcriptsErrors,
|
||||
videoSource: videoSourceErrors,
|
||||
duration: [durationErrors, setDurationErrors],
|
||||
handout: [handoutErrors, setHandoutErrors],
|
||||
license: [licenseErrors, setLicenseErrors],
|
||||
thumbnail: [thumbnailErrors, setThumbnailErrors],
|
||||
transcripts: [transcriptsErrors, setTranscriptsErrors],
|
||||
videoSource: [videoSourceErrors, setVideoSourceErrors],
|
||||
},
|
||||
validateEntry: () => {
|
||||
let validated = true;
|
||||
if (!module.validateDuration({ setDurationErrors })) { validated = false; }
|
||||
if (!module.validateHandout({ setHandoutErrors })) { validated = false; }
|
||||
if (!module.validateLicense({ setLicenseErrors })) { validated = false; }
|
||||
if (!module.validateThumbnail({ setThumbnailErrors })) { validated = false; }
|
||||
if (!module.validateTranscripts({ setTranscriptsErrors })) { validated = false; }
|
||||
if (!module.validateVideoSource({ setVideoSourceErrors })) { validated = false; }
|
||||
return validated;
|
||||
if (Object.keys(durationErrors).length > 0) { return false; }
|
||||
if (Object.keys(handoutErrors).length > 0) { return false; }
|
||||
if (Object.keys(licenseErrors).length > 0) { return false; }
|
||||
if (Object.keys(thumbnailErrors).length > 0) { return false; }
|
||||
if (Object.keys(transcriptsErrors).length > 0) { return false; }
|
||||
if (Object.keys(videoSourceErrors).length > 0) { return false; }
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const validateDuration = ({ setDurationErrors }) => {
|
||||
setDurationErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
export const validateHandout = ({ setHandoutErrors }) => {
|
||||
setHandoutErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
export const validateLicense = ({ setLicenseErrors }) => {
|
||||
setLicenseErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
export const validateThumbnail = ({ setThumbnailErrors }) => {
|
||||
setThumbnailErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
export const validateTranscripts = ({ setTranscriptsErrors }) => {
|
||||
setTranscriptsErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
export const validateVideoSource = ({ setVideoSourceErrors }) => {
|
||||
setVideoSourceErrors({
|
||||
fieldName: 'sample error message',
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MockUseState } from '../../../testUtils';
|
||||
|
||||
import { keyStore } from '../../utils';
|
||||
import * as module from './hooks';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
@@ -8,7 +7,6 @@ jest.mock('react', () => ({
|
||||
}));
|
||||
|
||||
const state = new MockUseState(module);
|
||||
const moduleKeys = keyStore(module);
|
||||
|
||||
let hook;
|
||||
|
||||
@@ -33,38 +31,44 @@ describe('VideoEditorHooks', () => {
|
||||
state.restore();
|
||||
});
|
||||
|
||||
const mockTrue = () => true;
|
||||
const mockFalse = () => false;
|
||||
const fakeDurationError = {
|
||||
field1: 'field1msg',
|
||||
field2: 'field2msg',
|
||||
};
|
||||
test('error: state values', () => {
|
||||
expect(module.errorsHook().error).toEqual({
|
||||
duration: state.stateVals[state.keys.durationErrors],
|
||||
handout: state.stateVals[state.keys.handoutErrors],
|
||||
license: state.stateVals[state.keys.licenseErrors],
|
||||
thumbnail: state.stateVals[state.keys.thumbnailErrors],
|
||||
transcripts: state.stateVals[state.keys.transcriptsErrors],
|
||||
videoSource: state.stateVals[state.keys.videoSourceErrors],
|
||||
});
|
||||
expect(module.errorsHook().error.duration).toEqual([
|
||||
state.stateVals[state.keys.durationErrors],
|
||||
state.setState[state.keys.durationErrors],
|
||||
]);
|
||||
expect(module.errorsHook().error.handout).toEqual([
|
||||
state.stateVals[state.keys.handoutErrors],
|
||||
state.setState[state.keys.handoutErrors],
|
||||
]);
|
||||
expect(module.errorsHook().error.license).toEqual([
|
||||
state.stateVals[state.keys.licenseErrors],
|
||||
state.setState[state.keys.licenseErrors],
|
||||
]);
|
||||
expect(module.errorsHook().error.thumbnail).toEqual([
|
||||
state.stateVals[state.keys.thumbnailErrors],
|
||||
state.setState[state.keys.thumbnailErrors],
|
||||
]);
|
||||
expect(module.errorsHook().error.transcripts).toEqual([
|
||||
state.stateVals[state.keys.transcriptsErrors],
|
||||
state.setState[state.keys.transcriptsErrors],
|
||||
]);
|
||||
expect(module.errorsHook().error.videoSource).toEqual([
|
||||
state.stateVals[state.keys.videoSourceErrors],
|
||||
state.setState[state.keys.videoSourceErrors],
|
||||
]);
|
||||
});
|
||||
describe('validateEntry', () => {
|
||||
beforeEach(() => {
|
||||
hook = module.errorsHook();
|
||||
});
|
||||
test('validateEntry: returns true if all validation calls are true', () => {
|
||||
jest.spyOn(module, moduleKeys.validateDuration).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateHandout).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateLicense).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateThumbnail).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateTranscripts).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateVideoSource).mockImplementationOnce(mockTrue);
|
||||
hook = module.errorsHook();
|
||||
expect(hook.validateEntry()).toEqual(true);
|
||||
});
|
||||
test('validateEntry: returns false if any validation calls are false', () => {
|
||||
jest.spyOn(module, moduleKeys.validateDuration).mockImplementationOnce(mockFalse);
|
||||
jest.spyOn(module, moduleKeys.validateHandout).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateLicense).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateThumbnail).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateTranscripts).mockImplementationOnce(mockTrue);
|
||||
jest.spyOn(module, moduleKeys.validateVideoSource).mockImplementationOnce(mockTrue);
|
||||
state.mockVal(state.keys.durationErrors, fakeDurationError);
|
||||
hook = module.errorsHook();
|
||||
expect(hook.validateEntry()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { selectors } from '../../data/redux';
|
||||
|
||||
import EditorContainer from '../EditorContainer';
|
||||
import VideoEditorModal from './components/VideoEditorModal';
|
||||
import { errorsHook } from './hooks';
|
||||
import { ErrorContext, errorsHook } from './hooks';
|
||||
|
||||
export const VideoEditor = ({
|
||||
onClose,
|
||||
@@ -19,15 +19,17 @@ export const VideoEditor = ({
|
||||
} = errorsHook();
|
||||
|
||||
return (
|
||||
<EditorContainer
|
||||
getContent={() => videoSettings}
|
||||
onClose={onClose}
|
||||
validateEntry={validateEntry}
|
||||
>
|
||||
<div className="video-editor">
|
||||
<VideoEditorModal {...{ error }} />
|
||||
</div>
|
||||
</EditorContainer>
|
||||
<ErrorContext.Provider value={error}>
|
||||
<EditorContainer
|
||||
getContent={() => videoSettings}
|
||||
onClose={onClose}
|
||||
validateEntry={validateEntry}
|
||||
>
|
||||
<div className="video-editor">
|
||||
<VideoEditorModal />
|
||||
</div>
|
||||
</EditorContainer>
|
||||
</ErrorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { selectors } from '../../data/redux';
|
||||
import { errorsHook } from './hooks';
|
||||
import { VideoEditor, mapStateToProps, mapDispatchToProps } from '.';
|
||||
|
||||
jest.mock('../EditorContainer', () => 'EditorContainer');
|
||||
jest.mock('./components/VideoEditorModal', () => 'VideoEditorModal');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
ErrorContext: jest.fn(),
|
||||
errorsHook: jest.fn(() => ({
|
||||
error: 'hooks.errorsHook.error',
|
||||
validateEntry: jest.fn().mockName('validateEntry'),
|
||||
@@ -29,10 +29,6 @@ describe('VideoEditor', () => {
|
||||
// redux
|
||||
videoSettings: 'vIdEOsETtings',
|
||||
};
|
||||
errorsHook.mockReturnValue({
|
||||
error: 'errORsHooKErroR',
|
||||
validateEntry: jest.fn().mockName('validateEntry'),
|
||||
});
|
||||
describe('snapshots', () => {
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<VideoEditor {...props} />)).toMatchSnapshot();
|
||||
|
||||
@@ -23,7 +23,7 @@ export const loadVideoData = () => (dispatch, getState) => {
|
||||
videoId,
|
||||
fallbackVideos,
|
||||
allowVideoDownloads: rawVideoData.download_video,
|
||||
transcripts: rawVideoData.transcripts,
|
||||
transcripts: rawVideoData.transcripts || {},
|
||||
allowTranscriptDownloads: rawVideoData.download_track,
|
||||
showTranscriptByDefault: rawVideoData.show_captions,
|
||||
duration: { // TODO duration is not always sent so they should be calculated.
|
||||
@@ -47,14 +47,28 @@ export const determineVideoSource = ({
|
||||
youtubeId,
|
||||
html5Sources,
|
||||
}) => {
|
||||
// videoSource should be the edx_video_id (if present), or the youtube url (if present), or the first fallback url.
|
||||
// in that order.
|
||||
// if we are falling back to the first fallback url, remove it from the list of fallback urls for display
|
||||
const videoSource = edxVideoId || youtubeId || html5Sources[0] || '';
|
||||
// videoSource should be the edx_video_id, the youtube url or the first fallback url in that order.
|
||||
// If we are falling back to the first fallback url, remove it from the list of fallback urls for display.
|
||||
const youtubeUrl = `https://youtu.be/${youtubeId}`;
|
||||
const videoId = edxVideoId || '';
|
||||
const fallbackVideos = (!edxVideoId && !youtubeId)
|
||||
? html5Sources.slice(1)
|
||||
: html5Sources;
|
||||
let videoSource = '';
|
||||
let fallbackVideos = [];
|
||||
if (edxVideoId) {
|
||||
[videoSource, fallbackVideos] = [edxVideoId, html5Sources];
|
||||
// videoSource = edxVideoId;
|
||||
// fallbackVideos = html5Sources;
|
||||
} else if (youtubeId) {
|
||||
[videoSource, fallbackVideos] = [youtubeUrl, html5Sources];
|
||||
// videoSource = youtubeUrl;
|
||||
// fallbackVideos = html5Sources;
|
||||
} else if (Array.isArray(html5Sources) && html5Sources[0]) {
|
||||
[videoSource, fallbackVideos] = [html5Sources[0], html5Sources.slice(1)];
|
||||
// videoSource = html5Sources[0];
|
||||
// fallbackVideos = html5Sources.slice(1);
|
||||
}
|
||||
if (fallbackVideos.length === 0) {
|
||||
fallbackVideos = ['', ''];
|
||||
}
|
||||
return {
|
||||
videoSource,
|
||||
videoId,
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('video thunkActions', () => {
|
||||
describe('determineVideoSource', () => {
|
||||
const edxVideoId = 'EDxviDEoiD';
|
||||
const youtubeId = 'yOuTuBEiD';
|
||||
const youtubeUrl = `https://youtu.be/${youtubeId}`;
|
||||
const html5Sources = ['htmLOne', 'hTMlTwo', 'htMLthrEE'];
|
||||
describe('when there is an edx video id, youtube id and html5 sources', () => {
|
||||
it('returns the edx video id for video source and html5 sources for fallback videos', () => {
|
||||
@@ -122,13 +123,13 @@ describe('video thunkActions', () => {
|
||||
});
|
||||
});
|
||||
describe('when there is no edx video id', () => {
|
||||
it('returns the youtube id for video source and html5 sources for fallback videos', () => {
|
||||
it('returns the youtube url for video source and html5 sources for fallback videos', () => {
|
||||
expect(thunkActions.determineVideoSource({
|
||||
edxVideoId: '',
|
||||
youtubeId,
|
||||
html5Sources,
|
||||
})).toEqual({
|
||||
videoSource: youtubeId,
|
||||
videoSource: youtubeUrl,
|
||||
videoId: '',
|
||||
fallbackVideos: html5Sources,
|
||||
});
|
||||
@@ -146,7 +147,7 @@ describe('video thunkActions', () => {
|
||||
fallbackVideos: ['hTMlTwo', 'htMLthrEE'],
|
||||
});
|
||||
});
|
||||
it('returns the html5 source for video source and an empty array for fallback videos', () => {
|
||||
it('returns the html5 source for video source and an array with 2 empty values for fallback videos', () => {
|
||||
expect(thunkActions.determineVideoSource({
|
||||
edxVideoId: '',
|
||||
youtubeId: '',
|
||||
@@ -154,12 +155,12 @@ describe('video thunkActions', () => {
|
||||
})).toEqual({
|
||||
videoSource: 'htmlOne',
|
||||
videoId: '',
|
||||
fallbackVideos: [],
|
||||
fallbackVideos: ['', ''],
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when there is no edx video id, no youtube id and no html5 sources', () => {
|
||||
it('returns an empty string for video source and an empty array for fallback videos', () => {
|
||||
it('returns an empty string for video source and an array with 2 empty values for fallback videos', () => {
|
||||
expect(thunkActions.determineVideoSource({
|
||||
edxVideoId: '',
|
||||
youtubeId: '',
|
||||
@@ -167,7 +168,7 @@ describe('video thunkActions', () => {
|
||||
})).toEqual({
|
||||
videoSource: '',
|
||||
videoId: '',
|
||||
fallbackVideos: [],
|
||||
fallbackVideos: ['', ''],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,9 @@ export const simpleSelectors = [
|
||||
export const openLanguages = createSelector(
|
||||
[module.simpleSelectors.transcripts],
|
||||
(transcripts) => {
|
||||
if (!transcripts) {
|
||||
return videoTranscriptLanguages;
|
||||
}
|
||||
const open = Object.entries(videoTranscriptLanguages).filter(
|
||||
([lang]) => !Object.keys(transcripts).includes(lang),
|
||||
);
|
||||
@@ -47,7 +50,19 @@ export const getTranscriptDownloadUrl = createSelector(
|
||||
);
|
||||
|
||||
export const videoSettings = createSelector(
|
||||
Object.values(module.simpleSelectors),
|
||||
[
|
||||
module.simpleSelectors.videoSource,
|
||||
module.simpleSelectors.fallbackVideos,
|
||||
module.simpleSelectors.allowVideoDownloads,
|
||||
module.simpleSelectors.thumbnail,
|
||||
module.simpleSelectors.transcripts,
|
||||
module.simpleSelectors.allowTranscriptDownloads,
|
||||
module.simpleSelectors.duration,
|
||||
module.simpleSelectors.showTranscriptByDefault,
|
||||
module.simpleSelectors.handout,
|
||||
module.simpleSelectors.licenseType,
|
||||
module.simpleSelectors.licenseDetails,
|
||||
],
|
||||
(
|
||||
videoSource,
|
||||
fallbackVideos,
|
||||
@@ -78,8 +93,8 @@ export const videoSettings = createSelector(
|
||||
);
|
||||
|
||||
export default {
|
||||
...simpleSelectors,
|
||||
openLanguages,
|
||||
getTranscriptDownloadUrl,
|
||||
...simpleSelectors,
|
||||
videoSettings,
|
||||
};
|
||||
|
||||
@@ -145,11 +145,11 @@ export const processVideoIds = ({ videoSource, fallbackVideos }) => {
|
||||
edxVideoId = videoSource;
|
||||
} else if (module.parseYoutubeId(videoSource)) {
|
||||
youtubeId = module.parseYoutubeId(videoSource);
|
||||
} else {
|
||||
} else if (videoSource) {
|
||||
html5Sources.push(videoSource);
|
||||
}
|
||||
|
||||
fallbackVideos.forEach((src) => html5Sources.push(src));
|
||||
fallbackVideos.forEach((src) => (src ? html5Sources.push(src) : null));
|
||||
|
||||
return {
|
||||
edxVideoId,
|
||||
|
||||
@@ -23,7 +23,7 @@ export const fetchBlockById = ({ blockId, studioEndpointUrl }) => mockPromise({
|
||||
sub: '',
|
||||
track: '',
|
||||
transcripts: {
|
||||
en: 'my-transcript-url',
|
||||
en: { filename: 'my-transcript-url' },
|
||||
},
|
||||
xml_attributes: {
|
||||
source: '',
|
||||
@@ -54,7 +54,7 @@ export const fetchStudioView = ({ blockId, studioEndpointUrl }) => mockPromise({
|
||||
sub: '',
|
||||
track: '',
|
||||
transcripts: {
|
||||
en: 'my-transcript-url',
|
||||
en: { filename: 'my-transcript-url' },
|
||||
},
|
||||
xml_attributes: {
|
||||
source: '',
|
||||
|
||||
Reference in New Issue
Block a user