feat: transcript parent widget component
This commit is contained in:
73
src/editors/sharedComponents/ErrorAlerts/ErrorAlert.jsx
Normal file
73
src/editors/sharedComponents/ErrorAlerts/ErrorAlert.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { Info } from '@edx/paragon/icons';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import messages from './messages';
|
||||
|
||||
export const hooks = {
|
||||
state: {
|
||||
isDismissed: (val) => React.useState(val),
|
||||
},
|
||||
dismissalHooks: ({ dismissError, isError }) => {
|
||||
const [isDismissed, setIsDismissed] = hooks.state.isDismissed(false);
|
||||
React.useEffect(() => {
|
||||
setIsDismissed(isDismissed && !isError);
|
||||
},
|
||||
[isError]);
|
||||
return {
|
||||
isDismissed,
|
||||
dismissAlert: () => {
|
||||
setIsDismissed(true);
|
||||
dismissError();
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorAlert = ({
|
||||
dismissError,
|
||||
hideHeading,
|
||||
isError,
|
||||
children,
|
||||
}) => {
|
||||
const { isDismissed, dismissAlert } = hooks.dismissalHooks({ dismissError, isError });
|
||||
if (!isError || isDismissed) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Alert
|
||||
variant="danger"
|
||||
icon={Info}
|
||||
dismissible
|
||||
onClose={dismissAlert}
|
||||
>
|
||||
{!hideHeading
|
||||
&& (
|
||||
<Alert.Heading>
|
||||
<FormattedMessage {...messages.errorTitle} />
|
||||
</Alert.Heading>
|
||||
)}
|
||||
{children}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorAlert.defaultProps = {
|
||||
dismissError: null,
|
||||
hideHeading: false,
|
||||
};
|
||||
|
||||
ErrorAlert.propTypes = {
|
||||
dismissError: PropTypes.func,
|
||||
hideHeading: PropTypes.bool,
|
||||
isError: PropTypes.bool.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
export default ErrorAlert;
|
||||
83
src/editors/sharedComponents/ErrorAlerts/ErrorAlert.test.jsx
Normal file
83
src/editors/sharedComponents/ErrorAlerts/ErrorAlert.test.jsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as module from './ErrorAlert';
|
||||
import { MockUseState } from '../../../testUtils';
|
||||
|
||||
const { ErrorAlert } = module;
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useRef: jest.fn(val => ({ current: val })),
|
||||
useEffect: jest.fn(),
|
||||
useCallback: (cb, prereqs) => ({ cb, prereqs }),
|
||||
}));
|
||||
|
||||
const state = new MockUseState(module.hooks);
|
||||
let hook;
|
||||
const testValue = 'testVALUE';
|
||||
|
||||
describe('ErrorAlert component', () => {
|
||||
describe('Hooks', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('state hooks', () => {
|
||||
state.testGetter(state.keys.isDismissed);
|
||||
});
|
||||
describe('using state', () => {
|
||||
beforeEach(() => { state.mock(); });
|
||||
afterEach(() => { state.restore(); });
|
||||
describe('dismissalHooks', () => {
|
||||
const props = {
|
||||
dismissError: jest.fn(),
|
||||
isError: testValue,
|
||||
};
|
||||
beforeEach(() => {
|
||||
hook = module.hooks.dismissalHooks(props);
|
||||
});
|
||||
it('returns isDismissed value, initialized to false', () => {
|
||||
expect(state.stateVals.isDismissed).toEqual(hook.isDismissed);
|
||||
});
|
||||
test('dismissAlert sets isDismissed to true and calls dismissError', () => {
|
||||
hook.dismissAlert();
|
||||
expect(state.setState.isDismissed).toHaveBeenCalledWith(true);
|
||||
expect(props.dismissError).toHaveBeenCalled();
|
||||
});
|
||||
test('On Render, calls setIsDismissed', () => {
|
||||
expect(React.useEffect.mock.calls.length).toEqual(1);
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs[0]).toEqual(testValue);
|
||||
cb();
|
||||
expect(state.setState.isDismissed).toHaveBeenCalledWith(state.stateVals.isDismissed && !testValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Component', () => {
|
||||
describe('Snapshots', () => {
|
||||
let props;
|
||||
const msg = <p> An Error Message </p>;
|
||||
beforeAll(() => {
|
||||
props = {
|
||||
dismissError: jest.fn(),
|
||||
};
|
||||
jest.spyOn(module.hooks, 'dismissalHooks').mockImplementation(() => ({
|
||||
isDismissed: false,
|
||||
dismissAlert: jest.fn().mockName('dismissAlert'),
|
||||
}));
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
test('snapshot: is Null when no error (ErrorAlert)', () => {
|
||||
expect(shallow(<ErrorAlert {...props}> <p> An Error Message </p> </ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: Loads children and component when error (ErrorAlert)', () => {
|
||||
expect(shallow(<ErrorAlert {...props} isError hideHeading={false}>{msg}</ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: Does not load heading when hideHeading is true', () => {
|
||||
expect(shallow(<ErrorAlert {...props} isError hideHeading>{msg}</ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
src/editors/sharedComponents/ErrorAlerts/FetchErrorAlert.jsx
Normal file
39
src/editors/sharedComponents/ErrorAlerts/FetchErrorAlert.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
import { selectors } from '../../data/redux';
|
||||
import { RequestKeys } from '../../data/constants/requests';
|
||||
|
||||
export const FetchErrorAlert = ({
|
||||
message,
|
||||
// redux
|
||||
isFetchError,
|
||||
// inject
|
||||
}) => (
|
||||
<ErrorAlert
|
||||
isError={isFetchError}
|
||||
>
|
||||
<FormattedMessage
|
||||
{...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 }),
|
||||
});
|
||||
export const mapDispatchToProps = {};
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FetchErrorAlert);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { FetchErrorAlert, mapStateToProps } from './FetchErrorAlert';
|
||||
import { selectors } from '../../data/redux';
|
||||
import { RequestKeys } from '../../data/constants/requests';
|
||||
|
||||
jest.mock('../../data/redux', () => ({
|
||||
selectors: {
|
||||
requests: {
|
||||
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('FetchErrorAlert', () => {
|
||||
describe('Snapshots', () => {
|
||||
test('snapshot: is ErrorAlert with Message error (ErrorAlert)', () => {
|
||||
expect(shallow(<FetchErrorAlert isFetchError />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
|
||||
test('isFetchError from requests.isFinished', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).isFetchError,
|
||||
).toEqual(selectors.requests.isFailed(testState, { requestKey: RequestKeys.fetchImages }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
import { selectors } from '../../data/redux';
|
||||
import { RequestKeys } from '../../data/constants/requests';
|
||||
|
||||
export const UploadErrorAlert = ({
|
||||
message,
|
||||
// redux
|
||||
isUploadError,
|
||||
// inject
|
||||
}) => (
|
||||
<ErrorAlert
|
||||
isError={isUploadError}
|
||||
>
|
||||
<FormattedMessage
|
||||
{...message}
|
||||
/>
|
||||
</ErrorAlert>
|
||||
);
|
||||
|
||||
UploadErrorAlert.propTypes = {
|
||||
message: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
defaultMessage: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
}).isRequired,
|
||||
// redux
|
||||
isUploadError: PropTypes.bool.isRequired,
|
||||
};
|
||||
export const mapStateToProps = (state) => ({
|
||||
isUploadError: selectors.requests.isFailed(state, { requestKey: RequestKeys.uploadImage }),
|
||||
});
|
||||
export const mapDispatchToProps = {};
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadErrorAlert);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { UploadErrorAlert, mapStateToProps } from './UploadErrorAlert';
|
||||
import { selectors } from '../../data/redux';
|
||||
import { RequestKeys } from '../../data/constants/requests';
|
||||
|
||||
jest.mock('../../data/redux', () => ({
|
||||
selectors: {
|
||||
requests: {
|
||||
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('UploadErrorAlert', () => {
|
||||
describe('Snapshots', () => {
|
||||
test('snapshot: is ErrorAlert with Message error (ErrorAlert)', () => {
|
||||
expect(shallow(<UploadErrorAlert isUploadError />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
|
||||
test('isUploadError from requests.isFinished', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).isUploadError,
|
||||
).toEqual(selectors.requests.isFailed(testState, { requestKey: RequestKeys.uploadImage }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: Does not load heading when hideHeading is true 1`] = `
|
||||
<Alert
|
||||
dismissible={true}
|
||||
onClose={[MockFunction dismissAlert]}
|
||||
variant="danger"
|
||||
>
|
||||
<p>
|
||||
An Error Message
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: Loads children and component when error (ErrorAlert) 1`] = `
|
||||
<Alert
|
||||
dismissible={true}
|
||||
onClose={[MockFunction dismissAlert]}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage
|
||||
defaultMessage="Error"
|
||||
description="Title of message presented to user when something goes wrong"
|
||||
id="authoring.texteditor.selectimagemodal.error.errorTitle"
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
An Error Message
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: is Null when no error (ErrorAlert) 1`] = `""`;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FetchErrorAlert Snapshots snapshot: is ErrorAlert with Message error (ErrorAlert) 1`] = `
|
||||
<ErrorAlert
|
||||
dismissError={null}
|
||||
hideHeading={false}
|
||||
isError={true}
|
||||
>
|
||||
<FormattedMessage />
|
||||
</ErrorAlert>
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UploadErrorAlert Snapshots snapshot: is ErrorAlert with Message error (ErrorAlert) 1`] = `
|
||||
<ErrorAlert
|
||||
dismissError={null}
|
||||
hideHeading={false}
|
||||
isError={true}
|
||||
>
|
||||
<FormattedMessage />
|
||||
</ErrorAlert>
|
||||
`;
|
||||
9
src/editors/sharedComponents/ErrorAlerts/messages.js
Normal file
9
src/editors/sharedComponents/ErrorAlerts/messages.js
Normal 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;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FileInput component snapshot 1`] = `
|
||||
<FileInput
|
||||
acceptedFiles=".srt"
|
||||
fileInput={
|
||||
Object {
|
||||
"addFile": [MockFunction props.fileInput.addFile],
|
||||
"ref": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
accept=".srt"
|
||||
className="upload d-none"
|
||||
onChange={[MockFunction props.fileInput.addFile]}
|
||||
type="file"
|
||||
/>
|
||||
</FileInput>
|
||||
`;
|
||||
27
src/editors/sharedComponents/FileInput/index.jsx
Normal file
27
src/editors/sharedComponents/FileInput/index.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const FileInput = ({ fileInput, acceptedFiles }) => (
|
||||
<input
|
||||
accept={acceptedFiles}
|
||||
className="upload d-none"
|
||||
onChange={fileInput.addFile}
|
||||
ref={fileInput.ref}
|
||||
type="file"
|
||||
/>
|
||||
);
|
||||
|
||||
FileInput.propTypes = {
|
||||
acceptedFiles: PropTypes.string.isRequired,
|
||||
fileInput: PropTypes.shape({
|
||||
addFile: PropTypes.func,
|
||||
ref: PropTypes.oneOfType([
|
||||
// Either a function
|
||||
PropTypes.func,
|
||||
// Or the instance of a DOM native element (see the note about SSR)
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default FileInput;
|
||||
33
src/editors/sharedComponents/FileInput/index.test.jsx
Normal file
33
src/editors/sharedComponents/FileInput/index.test.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { FileInput } from '.';
|
||||
|
||||
describe('FileInput component', () => {
|
||||
let el;
|
||||
let container;
|
||||
let props;
|
||||
beforeEach(() => {
|
||||
container = {};
|
||||
props = {
|
||||
acceptedFiles: '.srt',
|
||||
fileInput: {
|
||||
addFile: jest.fn().mockName('props.fileInput.addFile'),
|
||||
ref: (input) => { container.ref = input; },
|
||||
},
|
||||
};
|
||||
el = mount(<FileInput {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('only accepts allowed file types', () => {
|
||||
expect(el.find('input').props().accept).toEqual('.srt');
|
||||
});
|
||||
test('calls fileInput.addFile onChange', () => {
|
||||
expect(el.find('input').props().onChange).toEqual(props.fileInput.addFile);
|
||||
});
|
||||
test('loads ref from fileInput.ref', () => {
|
||||
expect(container.ref).toEqual(el.find('input').getDOMNode());
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user