feat: transcript parent widget component

This commit is contained in:
Kristin Aoki
2022-09-14 11:07:20 -04:00
committed by GitHub
parent 3f303a718d
commit ff636837cf
25 changed files with 811 additions and 114 deletions

View File

@@ -11,7 +11,7 @@ import messages from './messages';
import BaseModal from '../BaseModal';
import AltTextControls from './AltTextControls';
import DimensionControls from './DimensionControls';
import ErrorAlert from '../ErrorAlerts/ErrorAlert';
import ErrorAlert from '../../../../sharedComponents/ErrorAlerts/ErrorAlert';
/**
* Modal display wrapping the dimension and alt-text controls for image tags

View File

@@ -30,8 +30,24 @@ exports[`SelectImageModal component snapshot 1`] = `
isOpen={true}
title="Add an image"
>
<FetchErrorAlert />
<UploadErrorAlert />
<FetchErrorAlert
message={
Object {
"defaultMessage": "Failed to obtain course images. Please try again.",
"description": "Message presented to user when images are not found",
"id": "authoring.texteditor.selectimagemodal.error.fetchImagesError",
}
}
/>
<UploadErrorAlert
message={
Object {
"defaultMessage": "Failed to upload image. Please try again.",
"description": "Message presented to user when image fails to upload",
"id": "authoring.texteditor.selectimagemodal.error.uploadImageError",
}
}
/>
<ErrorAlert
dismissError={[MockFunction]}
hideHeading={true}
@@ -64,6 +80,7 @@ exports[`SelectImageModal component snapshot 1`] = `
gallery="props"
/>
<FileInput
acceptedFiles=".gif,.jpg,.jpeg,.png,.tif,.tiff,.ico"
fileInput={
Object {
"addFile": "imgHooks.fileInput.addFile",
@@ -106,8 +123,24 @@ exports[`SelectImageModal component snapshot: uploaded image not loaded, show sp
isOpen={true}
title="Add an image"
>
<FetchErrorAlert />
<UploadErrorAlert />
<FetchErrorAlert
message={
Object {
"defaultMessage": "Failed to obtain course images. Please try again.",
"description": "Message presented to user when images are not found",
"id": "authoring.texteditor.selectimagemodal.error.fetchImagesError",
}
}
/>
<UploadErrorAlert
message={
Object {
"defaultMessage": "Failed to upload image. Please try again.",
"description": "Message presented to user when image fails to upload",
"id": "authoring.texteditor.selectimagemodal.error.uploadImageError",
}
}
/>
<ErrorAlert
dismissError={[MockFunction]}
hideHeading={true}
@@ -142,6 +175,7 @@ exports[`SelectImageModal component snapshot: uploaded image not loaded, show sp
screenReaderText="loading..."
/>
<FileInput
acceptedFiles=".gif,.jpg,.jpeg,.png,.tif,.tiff,.ico"
fileInput={
Object {
"addFile": "imgHooks.fileInput.addFile",

View File

@@ -11,16 +11,17 @@ import {
} from '@edx/frontend-platform/i18n';
import { selectors } from '../../../../data/redux';
import { RequestKeys } from '../../../../data/constants/requests';
import { acceptedImgKeys } from './utils';
import hooks from './hooks';
import messages from './messages';
import BaseModal from '../BaseModal';
import SearchSort from './SearchSort';
import Gallery from './Gallery';
import FileInput from './FileInput';
import FetchErrorAlert from '../ErrorAlerts/FetchErrorAlert';
import UploadErrorAlert from '../ErrorAlerts/UploadErrorAlert';
import ErrorAlert from '../ErrorAlerts/ErrorAlert';
import FileInput from '../../../../sharedComponents/FileInput';
import FetchErrorAlert from '../../../../sharedComponents/ErrorAlerts/FetchErrorAlert';
import UploadErrorAlert from '../../../../sharedComponents/ErrorAlerts/UploadErrorAlert';
import ErrorAlert from '../../../../sharedComponents/ErrorAlerts/ErrorAlert';
export const SelectImageModal = ({
isOpen,
@@ -59,8 +60,8 @@ export const SelectImageModal = ({
title={intl.formatMessage(messages.titleLabel)}
>
{/* Error Alerts */}
<FetchErrorAlert />
<UploadErrorAlert />
<FetchErrorAlert message={messages.fetchImagesError} />
<UploadErrorAlert message={messages.uploadImageError} />
<ErrorAlert
dismissError={inputError.dismiss}
hideHeading
@@ -86,7 +87,7 @@ export const SelectImageModal = ({
screenReaderText={intl.formatMessage(messages.loading)}
/>
)}
<FileInput fileInput={fileInput} />
<FileInput fileInput={fileInput} acceptedFiles={Object.values(acceptedImgKeys).join()} />
</Stack>
</BaseModal>
);

View File

@@ -5,19 +5,19 @@ import { formatMessage } from '../../../../../testUtils';
import { RequestKeys } from '../../../../data/constants/requests';
import { selectors } from '../../../../data/redux';
import BaseModal from '../BaseModal';
import FileInput from './FileInput';
import FileInput from '../../../../sharedComponents/FileInput';
import Gallery from './Gallery';
import SearchSort from './SearchSort';
import hooks from './hooks';
import { SelectImageModal, mapStateToProps, mapDispatchToProps } from '.';
jest.mock('../BaseModal', () => 'BaseModal');
jest.mock('./FileInput', () => 'FileInput');
jest.mock('../../../../sharedComponents/FileInput', () => 'FileInput');
jest.mock('./Gallery', () => 'Gallery');
jest.mock('./SearchSort', () => 'SearchSort');
jest.mock('../ErrorAlerts/FetchErrorAlert', () => 'FetchErrorAlert');
jest.mock('../ErrorAlerts/UploadErrorAlert', () => 'UploadErrorAlert');
jest.mock('../ErrorAlerts/ErrorAlert', () => 'ErrorAlert');
jest.mock('../../../../sharedComponents/ErrorAlerts/FetchErrorAlert', () => 'FetchErrorAlert');
jest.mock('../../../../sharedComponents/ErrorAlerts/UploadErrorAlert', () => 'UploadErrorAlert');
jest.mock('../../../../sharedComponents/ErrorAlerts/ErrorAlert', () => 'ErrorAlert');
jest.mock('./hooks', () => ({
imgHooks: jest.fn(() => ({

View File

@@ -66,11 +66,6 @@ export const messages = {
},
// Errors
errorTitle: {
id: 'authoring.texteditor.selectimagemodal.error.errorTitle',
defaultMessage: 'Error',
description: 'Title of message presented to user when something goes wrong',
},
uploadImageError: {
id: 'authoring.texteditor.selectimagemodal.error.uploadImageError',
defaultMessage: 'Failed to upload image. Please try again.',

View File

@@ -0,0 +1,407 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TranscriptWidget snapshots snapshots: renders as expected with allowTranscriptDownloads true 1`] = `
<injectIntl(ShimmedIntlComponent)
isError={false}
subtitle="english"
title="Transcript"
>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to upload transcript. Please try again."
description="Message presented to user when transcript fails to upload"
id="authoring.videoeditor.transcript.error.uploadTranscriptError"
/>
</ErrorAlert>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Transcript file size exeeds the maximum. Please try again."
description="Message presented to user when transcript file size is too large"
id="authoring.videoeditor.transcript.error.fileSizeError"
/>
</ErrorAlert>
<Stack
gap={3}
>
<Form.Group
className="mt-4.5"
>
<b>
Transcript widget:
</b>
<div
className="mb-1"
>
<Form.Checkbox
checked={true}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label>
<FormattedMessage
defaultMessage="Allow transcript downloads"
description="Label for allow transcript downloads checkbox"
id="authoring.videoeditor.transcripts.allowDownloadCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
<Component
key="right"
overlay={
<UNDEFINED>
<FormattedMessage
defaultMessage="Learners will see a link to download the transcript below the video."
description="Message for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.allowDownloadTooltipMessage"
/>
</UNDEFINED>
}
placement="right"
>
<Icon
className="d-inline-block mx-3"
/>
</Component>
</div>
<Form.Checkbox
checked={false}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label
size="sm"
>
<FormattedMessage
defaultMessage="Show transcript in the video player by default"
description="Label for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.showByDefaultCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
</Form.Group>
<FileInput
acceptedFiles=".srt"
fileInput={
Object {
"addFile": [Function],
"click": [Function],
"ref": Object {
"current": undefined,
},
}
}
/>
<Button
onClick={[Function]}
variant="link"
>
<FormattedMessage
defaultMessage="Upload Transcript"
description="Label for upload button"
id="authoring.videoeditor.transcripts.upload.label"
/>
</Button>
</Stack>
</injectIntl(ShimmedIntlComponent)>
`;
exports[`TranscriptWidget snapshots snapshots: renders as expected with default props 1`] = `
<injectIntl(ShimmedIntlComponent)
isError={false}
subtitle="None"
title="Transcript"
>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to upload transcript. Please try again."
description="Message presented to user when transcript fails to upload"
id="authoring.videoeditor.transcript.error.uploadTranscriptError"
/>
</ErrorAlert>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Transcript file size exeeds the maximum. Please try again."
description="Message presented to user when transcript file size is too large"
id="authoring.videoeditor.transcript.error.fileSizeError"
/>
</ErrorAlert>
<Stack
gap={3}
>
<Alert
variant="danger"
>
Only SRT files can be uploaded. Please select a file ending in .srt to upload.
</Alert>
<FormattedMessage
defaultMessage="Add video transcripts for improved accessibility."
description="Message for adding first transcript"
id="authoring.videoeditor.transcripts.upload.firstTranscriptMessage"
/>
<FileInput
acceptedFiles=".srt"
fileInput={
Object {
"addFile": [Function],
"click": [Function],
"ref": Object {
"current": undefined,
},
}
}
/>
<Button
onClick={[Function]}
variant="link"
>
<FormattedMessage
defaultMessage="Upload Transcript"
description="Label for upload button"
id="authoring.videoeditor.transcripts.upload.label"
/>
</Button>
</Stack>
</injectIntl(ShimmedIntlComponent)>
`;
exports[`TranscriptWidget snapshots snapshots: renders as expected with showTranscriptByDefault true 1`] = `
<injectIntl(ShimmedIntlComponent)
isError={false}
subtitle="english"
title="Transcript"
>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to upload transcript. Please try again."
description="Message presented to user when transcript fails to upload"
id="authoring.videoeditor.transcript.error.uploadTranscriptError"
/>
</ErrorAlert>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Transcript file size exeeds the maximum. Please try again."
description="Message presented to user when transcript file size is too large"
id="authoring.videoeditor.transcript.error.fileSizeError"
/>
</ErrorAlert>
<Stack
gap={3}
>
<Form.Group
className="mt-4.5"
>
<b>
Transcript widget:
</b>
<div
className="mb-1"
>
<Form.Checkbox
checked={false}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label>
<FormattedMessage
defaultMessage="Allow transcript downloads"
description="Label for allow transcript downloads checkbox"
id="authoring.videoeditor.transcripts.allowDownloadCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
<Component
key="right"
overlay={
<UNDEFINED>
<FormattedMessage
defaultMessage="Learners will see a link to download the transcript below the video."
description="Message for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.allowDownloadTooltipMessage"
/>
</UNDEFINED>
}
placement="right"
>
<Icon
className="d-inline-block mx-3"
/>
</Component>
</div>
<Form.Checkbox
checked={true}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label
size="sm"
>
<FormattedMessage
defaultMessage="Show transcript in the video player by default"
description="Label for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.showByDefaultCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
</Form.Group>
<FileInput
acceptedFiles=".srt"
fileInput={
Object {
"addFile": [Function],
"click": [Function],
"ref": Object {
"current": undefined,
},
}
}
/>
<Button
onClick={[Function]}
variant="link"
>
<FormattedMessage
defaultMessage="Upload Transcript"
description="Label for upload button"
id="authoring.videoeditor.transcripts.upload.label"
/>
</Button>
</Stack>
</injectIntl(ShimmedIntlComponent)>
`;
exports[`TranscriptWidget snapshots snapshots: renders as expected with transcripts 1`] = `
<injectIntl(ShimmedIntlComponent)
isError={false}
subtitle="english"
title="Transcript"
>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to upload transcript. Please try again."
description="Message presented to user when transcript fails to upload"
id="authoring.videoeditor.transcript.error.uploadTranscriptError"
/>
</ErrorAlert>
<ErrorAlert
dismissError={[Function]}
hideHeading={true}
isError={true}
>
<FormattedMessage
defaultMessage="Transcript file size exeeds the maximum. Please try again."
description="Message presented to user when transcript file size is too large"
id="authoring.videoeditor.transcript.error.fileSizeError"
/>
</ErrorAlert>
<Stack
gap={3}
>
<Form.Group
className="mt-4.5"
>
<b>
Transcript widget:
</b>
<div
className="mb-1"
>
<Form.Checkbox
checked={false}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label>
<FormattedMessage
defaultMessage="Allow transcript downloads"
description="Label for allow transcript downloads checkbox"
id="authoring.videoeditor.transcripts.allowDownloadCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
<Component
key="right"
overlay={
<UNDEFINED>
<FormattedMessage
defaultMessage="Learners will see a link to download the transcript below the video."
description="Message for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.allowDownloadTooltipMessage"
/>
</UNDEFINED>
}
placement="right"
>
<Icon
className="d-inline-block mx-3"
/>
</Component>
</div>
<Form.Checkbox
checked={false}
className="mt-4.5 decorative-control-label"
onChange={[Function]}
>
<Form.Label
size="sm"
>
<FormattedMessage
defaultMessage="Show transcript in the video player by default"
description="Label for show by default checkbox"
id="authoring.videoeditor.transcripts.upload.showByDefaultCheckboxLabel"
/>
</Form.Label>
</Form.Checkbox>
</Form.Group>
<FileInput
acceptedFiles=".srt"
fileInput={
Object {
"addFile": [Function],
"click": [Function],
"ref": Object {
"current": undefined,
},
}
}
/>
<Button
onClick={[Function]}
variant="link"
>
<FormattedMessage
defaultMessage="Upload Transcript"
description="Label for upload button"
id="authoring.videoeditor.transcripts.upload.label"
/>
</Button>
</Stack>
</injectIntl(ShimmedIntlComponent)>
`;

View File

@@ -0,0 +1,29 @@
import React from 'react';
export const transcriptLanguages = (transcripts) => {
const languages = [];
if (transcripts) {
Object.keys(transcripts).forEach(transcript => {
languages.push(transcript);
});
return languages.join(', ');
}
return 'None';
};
export const fileInput = () => {
const ref = React.useRef();
const click = () => ref.current.click();
const addFile = (e) => {
const selectedFile = e.target.files[0];
console.log(selectedFile);
};
return {
click,
addFile,
ref,
};
};
export default { transcriptLanguages, fileInput };

View File

@@ -0,0 +1,148 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Form,
Button,
Stack,
Icon,
OverlayTrigger,
Tooltip,
Alert,
} from '@edx/paragon';
import { FileUpload, Info } from '@edx/paragon/icons';
import {
FormattedMessage,
injectIntl,
} from '@edx/frontend-platform/i18n';
import { actions, selectors } from '../../../../../../data/redux';
import * as hooks from './hooks';
import messages from './messages';
import FileInput from '../../../../../../sharedComponents/FileInput';
import ErrorAlert from '../../../../../../sharedComponents/ErrorAlerts/ErrorAlert';
import CollapsibleFormWidget from '../CollapsibleFormWidget';
/**
* Collapsible Form widget controlling video transcripts
*/
export const TranscriptWidget = ({
error,
// redux
transcripts,
allowTranscriptDownloads,
showTranscriptByDefault,
updateField,
}) => {
const languagesArr = hooks.transcriptLanguages(transcripts);
const fileInput = hooks.fileInput();
const input = {
error: {
dismiss: () => { console.log('dismiss'); },
show: true,
},
};
const upload = {
error: {
dismiss: () => { console.log('dismiss'); },
show: true,
},
};
return (
<CollapsibleFormWidget
isError={Object.keys(error).length !== 0}
subtitle={languagesArr}
title="Transcript"
>
<ErrorAlert
dismissError={upload.error.dismiss}
hideHeading
isError={upload.error.show}
>
<FormattedMessage {...messages.uploadTranscriptError} />
</ErrorAlert>
<ErrorAlert
dismissError={input.error.dismiss}
hideHeading
isError={input.error.show}
>
<FormattedMessage {...messages.fileSizeError} />
</ErrorAlert>
<Stack gap={3}>
{transcripts ? (
<Form.Group className="mt-4.5">
<b>Transcript widget:</b>
<div className="mb-1">
<Form.Checkbox
checked={allowTranscriptDownloads}
className="mt-4.5 decorative-control-label"
onChange={(e) => updateField({ allowTranscriptDownloads: e.target.checked })}
>
<Form.Label>
<FormattedMessage {...messages.allowDownloadCheckboxLabel} />
</Form.Label>
</Form.Checkbox>
<OverlayTrigger
key="right"
placement="right"
overlay={(
<Tooltip>
<FormattedMessage {...messages.tooltipMessage} />
</Tooltip>
)}
>
<Icon className="d-inline-block mx-3" src={Info} />
</OverlayTrigger>
</div>
<Form.Checkbox
checked={showTranscriptByDefault}
className="mt-4.5 decorative-control-label"
onChange={(e) => updateField({ showTranscriptByDefault: e.target.checked })}
>
<Form.Label size="sm">
<FormattedMessage {...messages.showByDefaultCheckboxLabel} />
</Form.Label>
</Form.Checkbox>
</Form.Group>
) : (
<>
<Alert variant="danger" icon={Info}>
Only SRT files can be uploaded. Please select a file ending in .srt to upload.
</Alert>
<FormattedMessage {...messages.addFirstTranscript} />
</>
)}
<FileInput fileInput={fileInput} acceptedFiles=".srt" />
<Button iconBefore={FileUpload} onClick={fileInput.click} variant="link">
<FormattedMessage {...messages.uploadButtonLabel} />
</Button>
</Stack>
</CollapsibleFormWidget>
);
};
TranscriptWidget.defaultProps = {
error: {},
};
TranscriptWidget.propTypes = {
error: PropTypes.node,
// redux
transcripts: PropTypes.shape({}).isRequired,
allowTranscriptDownloads: PropTypes.bool.isRequired,
showTranscriptByDefault: PropTypes.bool.isRequired,
updateField: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => ({
transcripts: selectors.video.transcripts(state),
allowTranscriptDownloads: selectors.video.allowTranscriptDownloads(state),
showTranscriptByDefault: selectors.video.showTranscriptByDefault(state),
});
export const mapDispatchToProps = (dispatch) => ({
updateField: (stateUpdate) => dispatch(actions.video.updateField(stateUpdate)),
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TranscriptWidget));

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { shallow } from 'enzyme';
import { formatMessage } from '../../../../../../../testUtils';
import { actions, selectors } from '../../../../../../data/redux';
import { TranscriptWidget, mapStateToProps, mapDispatchToProps } from '.';
jest.mock('../../../../../../data/redux', () => ({
actions: {
video: {
updateField: jest.fn().mockName('actions.video.updateField'),
},
},
selectors: {
video: {
transcripts: jest.fn(state => ({ transcripts: state })),
allowTranscriptDownloads: jest.fn(state => ({ allowTranscriptDownloads: state })),
showTranscriptByDefault: jest.fn(state => ({ showTranscriptByDefault: state })),
},
},
}));
describe('TranscriptWidget', () => {
const props = {
error: {},
subtitle: 'SuBTItle',
title: 'tiTLE',
// inject
intl: { formatMessage },
// redux
transcripts: null,
allowTranscriptDownloads: false,
showTranscriptByDefault: false,
updateField: jest.fn().mockName('args.updateField'),
};
describe('snapshots', () => {
test('snapshots: renders as expected with default props', () => {
expect(
shallow(<TranscriptWidget {...props} />),
).toMatchSnapshot();
});
test('snapshots: renders as expected with transcripts', () => {
expect(
shallow(<TranscriptWidget {...props} transcripts={{ english: 'sOMeUrl' }} />),
).toMatchSnapshot();
});
test('snapshots: renders as expected with allowTranscriptDownloads true', () => {
expect(
shallow(<TranscriptWidget {...props} allowTranscriptDownloads transcripts={{ english: 'sOMeUrl' }} />),
).toMatchSnapshot();
});
test('snapshots: renders as expected with showTranscriptByDefault true', () => {
expect(
shallow(<TranscriptWidget {...props} showTranscriptByDefault transcripts={{ english: 'sOMeUrl' }} />),
).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('transcripts from video.transcript', () => {
expect(
mapStateToProps(testState).transcripts,
).toEqual(selectors.video.transcripts(testState));
});
test('allowTranscriptDownloads from video.allowTranscriptDownloads', () => {
expect(
mapStateToProps(testState).allowTranscriptDownloads,
).toEqual(selectors.video.allowTranscriptDownloads(testState));
});
test('showTranscriptByDefault from video.showTranscriptByDefault', () => {
expect(
mapStateToProps(testState).showTranscriptByDefault,
).toEqual(selectors.video.showTranscriptByDefault(testState));
});
});
describe('mapDispatchToProps', () => {
const dispatch = jest.fn();
test('updateField from actions.video.updateField', () => {
expect(mapDispatchToProps.updateField).toEqual(dispatch(actions.video.updateField));
});
});
});

View File

@@ -0,0 +1,44 @@
export const messages = {
uploadButtonLabel: {
id: 'authoring.videoeditor.transcripts.upload.label',
defaultMessage: 'Upload Transcript',
description: 'Label for upload button',
},
addFirstTranscript: {
id: 'authoring.videoeditor.transcripts.upload.firstTranscriptMessage',
defaultMessage: 'Add video transcripts for improved accessibility.',
description: 'Message for adding first transcript',
},
allowDownloadCheckboxLabel: {
id: 'authoring.videoeditor.transcripts.allowDownloadCheckboxLabel',
defaultMessage: 'Allow transcript downloads',
description: 'Label for allow transcript downloads checkbox',
},
showByDefaultCheckboxLabel: {
id: 'authoring.videoeditor.transcripts.upload.showByDefaultCheckboxLabel',
defaultMessage: 'Show transcript in the video player by default',
description: 'Label for show by default checkbox',
},
tooltipMessage: {
id: 'authoring.videoeditor.transcripts.upload.allowDownloadTooltipMessage',
defaultMessage: 'Learners will see a link to download the transcript below the video.',
description: 'Message for show by default checkbox',
},
transcriptTypeError: {
id: 'authoring.videoeditor.transcript.error.transcriptTypeError',
defaultMessage: 'Only SRT file can be uploaded',
description: 'Message presented to user when image fails to upload',
},
uploadTranscriptError: {
id: 'authoring.videoeditor.transcript.error.uploadTranscriptError',
defaultMessage: 'Failed to upload transcript. Please try again.',
description: 'Message presented to user when transcript fails to upload',
},
fileSizeError: {
id: 'authoring.videoeditor.transcript.error.fileSizeError',
defaultMessage: 'Transcript file size exeeds the maximum. Please try again.',
description: 'Message presented to user when transcript file size is too large',
},
};
export default messages;

View File

@@ -1,53 +0,0 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import hooks from './hooks';
import CollapsibleFormWidget from './CollapsibleFormWidget';
/**
* Collapsible Form widget controlling video transcripts
*/
export const TranscriptWidget = ({
error,
}) => {
const dispatch = useDispatch();
const values = hooks.widgetValues({
dispatch,
fields: {
[hooks.selectorKeys.transcripts]: hooks.objectWidget,
[hooks.selectorKeys.allowTranscriptDownloads]: hooks.genericWidget,
[hooks.selectorKeys.showTranscriptByDefault]: hooks.genericWidget,
},
});
const {
transcripts,
allowTranscriptDownloads: allowDownload,
showTranscriptByDefault: showByDefault,
} = values;
// TODO: replace the following sample subtitle input with one managed by hook logic
const sampleSubtitle = <div>{transcripts.formValue.english}</div>;
return (
<CollapsibleFormWidget
isError={Object.keys(error).length !== 0}
subtitle={sampleSubtitle}
title="Transcript"
>
<b>Transcripts</b>
<p>English: {transcripts.formValue.english}</p>
<p><b>Allow downloads:</b> {allowDownload.formValue ? 'True' : 'False' }</p>
<p><b>Show by default:</b> {showByDefault.formValue ? 'True' : 'False' }</p>
</CollapsibleFormWidget>
);
};
TranscriptWidget.defaultProps = {
error: {},
};
TranscriptWidget.propTypes = {
error: PropTypes.node,
};
export default TranscriptWidget;

View File

@@ -8,7 +8,7 @@ import DurationWidget from './components/DurationWidget';
import HandoutWidget from './components/HandoutWidget';
import LicenseWidget from './components/LicenseWidget';
import ThumbnailWidget from './components/ThumbnailWidget';
import TranscriptsWidget from './components/TranscriptsWidget';
import TranscriptWidget from './components/TranscriptWidget';
import VideoSourceWidget from './components/VideoSourceWidget';
import './index.scss';
@@ -33,7 +33,7 @@ export const VideoSettingsModal = ({
<h3>Settings</h3>
<VideoSourceWidget error={error.videoSource} />
<ThumbnailWidget error={error.thumbnail} />
<TranscriptsWidget error={error.transcripts} />
<TranscriptWidget error={error.transcripts} />
<DurationWidget error={error.duration} />
<HandoutWidget error={error.handout} />
<LicenseWidget error={error.license} />

View File

@@ -5,7 +5,7 @@ import { Alert } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from '../SelectImageModal/messages';
import messages from './messages';
export const hooks = {
state: {

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import * as module from './ErrorAlert';
import { MockUseState } from '../../../../../testUtils';
import { MockUseState } from '../../../testUtils';
const { ErrorAlert } = module;

View File

@@ -3,12 +3,12 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from '../SelectImageModal/messages';
import ErrorAlert from './ErrorAlert';
import { selectors } from '../../../../data/redux';
import { RequestKeys } from '../../../../data/constants/requests';
import { selectors } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
export const FetchErrorAlert = ({
message,
// redux
isFetchError,
// inject
@@ -17,14 +17,20 @@ export const FetchErrorAlert = ({
isError={isFetchError}
>
<FormattedMessage
{...messages.fetchImagesError}
{...message}
/>
</ErrorAlert>
);
FetchErrorAlert.propTypes = {
message: PropTypes.shape({
id: PropTypes.string,
defaultMessage: PropTypes.string,
description: PropTypes.string,
}).isRequired,
// redux
isFetchError: PropTypes.bool.isRequired,
};
export const mapStateToProps = (state) => ({
isFetchError: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchImages }),

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import { FetchErrorAlert, mapStateToProps } from './FetchErrorAlert';
import { selectors } from '../../../../data/redux';
import { RequestKeys } from '../../../../data/constants/requests';
import { selectors } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
jest.mock('../../../../data/redux', () => ({
jest.mock('../../data/redux', () => ({
selectors: {
requests: {
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),

View File

@@ -3,12 +3,12 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from '../SelectImageModal/messages';
import ErrorAlert from './ErrorAlert';
import { selectors } from '../../../../data/redux';
import { RequestKeys } from '../../../../data/constants/requests';
import { selectors } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
export const UploadErrorAlert = ({
message,
// redux
isUploadError,
// inject
@@ -17,12 +17,17 @@ export const UploadErrorAlert = ({
isError={isUploadError}
>
<FormattedMessage
{...messages.uploadImageError}
{...message}
/>
</ErrorAlert>
);
UploadErrorAlert.propTypes = {
message: PropTypes.shape({
id: PropTypes.string,
defaultMessage: PropTypes.string,
description: PropTypes.string,
}).isRequired,
// redux
isUploadError: PropTypes.bool.isRequired,
};

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import { UploadErrorAlert, mapStateToProps } from './UploadErrorAlert';
import { selectors } from '../../../../data/redux';
import { RequestKeys } from '../../../../data/constants/requests';
import { selectors } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
jest.mock('../../../../data/redux', () => ({
jest.mock('../../data/redux', () => ({
selectors: {
requests: {
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),

View File

@@ -6,10 +6,6 @@ exports[`FetchErrorAlert Snapshots snapshot: is ErrorAlert with Message error (
hideHeading={false}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to obtain course images. Please try again."
description="Message presented to user when images are not found"
id="authoring.texteditor.selectimagemodal.error.fetchImagesError"
/>
<FormattedMessage />
</ErrorAlert>
`;

View File

@@ -6,10 +6,6 @@ exports[`UploadErrorAlert Snapshots snapshot: is ErrorAlert with Message error
hideHeading={false}
isError={true}
>
<FormattedMessage
defaultMessage="Failed to upload image. Please try again."
description="Message presented to user when image fails to upload"
id="authoring.texteditor.selectimagemodal.error.uploadImageError"
/>
<FormattedMessage />
</ErrorAlert>
`;

View File

@@ -0,0 +1,9 @@
export const messages = {
errorTitle: {
id: 'authoring.texteditor.selectimagemodal.error.errorTitle',
defaultMessage: 'Error',
description: 'Title of message presented to user when something goes wrong',
},
};
export default messages;

View File

@@ -2,6 +2,7 @@
exports[`FileInput component snapshot 1`] = `
<FileInput
acceptedFiles=".srt"
fileInput={
Object {
"addFile": [MockFunction props.fileInput.addFile],
@@ -10,7 +11,7 @@ exports[`FileInput component snapshot 1`] = `
}
>
<input
accept=".gif,.jpg,.jpeg,.png,.tif,.tiff,.ico"
accept=".srt"
className="upload d-none"
onChange={[MockFunction props.fileInput.addFile]}
type="file"

View File

@@ -1,11 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { acceptedImgKeys } from './utils';
export const FileInput = ({ fileInput }) => (
export const FileInput = ({ fileInput, acceptedFiles }) => (
<input
accept={Object.values(acceptedImgKeys).join()}
accept={acceptedFiles}
className="upload d-none"
onChange={fileInput.addFile}
ref={fileInput.ref}
@@ -14,6 +12,7 @@ export const FileInput = ({ fileInput }) => (
);
FileInput.propTypes = {
acceptedFiles: PropTypes.string.isRequired,
fileInput: PropTypes.shape({
addFile: PropTypes.func,
ref: PropTypes.oneOfType([

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { acceptedImgKeys } from './utils';
import { FileInput } from './FileInput';
import { FileInput } from '.';
describe('FileInput component', () => {
let el;
@@ -12,6 +10,7 @@ describe('FileInput component', () => {
beforeEach(() => {
container = {};
props = {
acceptedFiles: '.srt',
fileInput: {
addFile: jest.fn().mockName('props.fileInput.addFile'),
ref: (input) => { container.ref = input; },
@@ -23,9 +22,7 @@ describe('FileInput component', () => {
expect(el).toMatchSnapshot();
});
test('only accepts allowed file types', () => {
expect(el.find('input').props().accept).toEqual(
Object.values(acceptedImgKeys).join(),
);
expect(el.find('input').props().accept).toEqual('.srt');
});
test('calls fileInput.addFile onChange', () => {
expect(el.find('input').props().onChange).toEqual(props.fileInput.addFile);