feat: revert to connect
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
exports[`DurationWidget render snapshots: renders as expected with default props 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
fontSize="x-small"
|
||||
subtitle="Full video length"
|
||||
subtitle="Total: 00:00:00"
|
||||
title="Duration"
|
||||
>
|
||||
<FormattedMessage
|
||||
@@ -50,7 +50,7 @@ exports[`DurationWidget render snapshots: renders as expected with default props
|
||||
<div
|
||||
className="mt-4"
|
||||
>
|
||||
Full video length
|
||||
Total: 00:00:00
|
||||
</div>
|
||||
</injectIntl(ShimmedIntlComponent)>
|
||||
`;
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { actions, selectors } from '../../../../../../data/redux';
|
||||
import messages from '../messages';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
const durationMatcher = /^(\d{0,2}):?(\d{0,2})?:?(\d{0,2})?$/i;
|
||||
|
||||
export const durationWidget = ({ dispatch }) => {
|
||||
const reduxStartStopTimes = useSelector(selectors.video.duration);
|
||||
const setReduxStartStopTimes = (val) => dispatch(actions.video.updateField({ duration: val }));
|
||||
const initialState = module.durationString(reduxStartStopTimes);
|
||||
const [unsavedStartStopTimes, setUnsavedStartStopTimes] = useState(initialState);
|
||||
export const durationWidget = ({ duration, updateField }) => {
|
||||
const setDuration = (val) => updateField({ duration: val });
|
||||
const initialState = module.durationString(duration);
|
||||
const [unsavedDuration, setUnsavedDuration] = useState(initialState);
|
||||
|
||||
useEffect(() => {
|
||||
setUnsavedStartStopTimes(module.durationString(reduxStartStopTimes));
|
||||
}, [reduxStartStopTimes]);
|
||||
setUnsavedDuration(module.durationString(duration));
|
||||
}, [duration]);
|
||||
|
||||
return {
|
||||
reduxStartStopTimes,
|
||||
unsavedStartStopTimes,
|
||||
unsavedDuration,
|
||||
onBlur: (index) => (
|
||||
(e) => module.updateDuration({
|
||||
reduxStartStopTimes,
|
||||
setReduxStartStopTimes,
|
||||
unsavedStartStopTimes,
|
||||
setUnsavedStartStopTimes,
|
||||
duration,
|
||||
setDuration,
|
||||
unsavedDuration,
|
||||
setUnsavedDuration,
|
||||
index,
|
||||
durationString: e.target.value,
|
||||
})
|
||||
),
|
||||
onChange: (index) => (
|
||||
(e) => setUnsavedStartStopTimes(module.onDurationChange(unsavedStartStopTimes, index, e.target.value))
|
||||
(e) => setUnsavedDuration(module.onDurationChange(unsavedDuration, index, e.target.value))
|
||||
),
|
||||
onKeyDown: (index) => (
|
||||
(e) => setUnsavedStartStopTimes(module.onDurationKeyDown(unsavedStartStopTimes, index, e))
|
||||
(e) => setUnsavedDuration(module.onDurationKeyDown(unsavedDuration, index, e))
|
||||
),
|
||||
getTotalLabel: ({ duration, subtitle, intl }) => {
|
||||
if (!duration.stopTime) {
|
||||
@@ -86,23 +82,23 @@ export const durationStringFromValue = (value) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* updateDuration({ reduxStartStopTimes, unsavedStartStopTimes, setUnsavedStartStopTimes, setReduxStartStopTimes })
|
||||
* Returns a memoized callback based on inputs that updates unsavedStartStopTimes value and form value
|
||||
* if the new string is valid (reduxStartStopTimes stores a number, unsavedStartStopTimes stores a string).
|
||||
* If the duration string is invalid, resets the unsavedStartStopTimes value to the latest good value.
|
||||
* @param {object} reduxStartStopTimes - redux-stored durations in milliseconds
|
||||
* @param {object} unsavedStartStopTimes - hook-stored duration in 'hh:mm:ss' format
|
||||
* @param {func} setReduxStartStopTimes - set form value
|
||||
* @param {func} setUnsavedStartStopTimes - set unsavedStartStopTimes object
|
||||
* updateDuration({ duration, unsavedDuration, setUnsavedDuration, setDuration })
|
||||
* Returns a memoized callback based on inputs that updates unsavedDuration value and form value
|
||||
* if the new string is valid (duration stores a number, unsavedDuration stores a string).
|
||||
* If the duration string is invalid, resets the unsavedDuration value to the latest good value.
|
||||
* @param {object} duration - redux-stored durations in milliseconds
|
||||
* @param {object} unsavedDuration - hook-stored duration in 'hh:mm:ss' format
|
||||
* @param {func} setDuration - set form value
|
||||
* @param {func} setUnsavedDuration - set unsavedDuration object
|
||||
* @param {string} index - startTime or stopTime
|
||||
* @return {func} - callback to update duration unsavedStartStopTimesly and in redux
|
||||
* @return {func} - callback to update duration unsavedDurationly and in redux
|
||||
* updateDuration(args)(index, durationString)
|
||||
*/
|
||||
export const updateDuration = ({
|
||||
reduxStartStopTimes,
|
||||
unsavedStartStopTimes,
|
||||
setReduxStartStopTimes,
|
||||
setUnsavedStartStopTimes,
|
||||
duration,
|
||||
unsavedDuration,
|
||||
setDuration,
|
||||
setUnsavedDuration,
|
||||
index,
|
||||
inputString,
|
||||
}) => {
|
||||
@@ -117,17 +113,17 @@ export const updateDuration = ({
|
||||
newValue = 1000;
|
||||
}
|
||||
// stopTime must be at least 1 second after startTime, except 0 means no custom stopTime
|
||||
if (index === 'stopTime' && newValue > 0 && newValue < (reduxStartStopTimes.startTime + 1000)) {
|
||||
newValue = reduxStartStopTimes.startTime + 1000;
|
||||
if (index === 'stopTime' && newValue > 0 && newValue < (duration.startTime + 1000)) {
|
||||
newValue = duration.startTime + 1000;
|
||||
}
|
||||
// startTime must be at least 1 second before stopTime, except when stopTime is less than a second
|
||||
// (stopTime should only be less than a second if it's zero, but we're being paranoid)
|
||||
if (index === 'startTime' && reduxStartStopTimes.stopTime >= 1000 && newValue > (reduxStartStopTimes.stopTime - 1000)) {
|
||||
newValue = reduxStartStopTimes.stopTime - 1000;
|
||||
if (index === 'startTime' && duration.stopTime >= 1000 && newValue > (duration.stopTime - 1000)) {
|
||||
newValue = duration.stopTime - 1000;
|
||||
}
|
||||
newDurationString = module.durationStringFromValue(newValue);
|
||||
setUnsavedStartStopTimes({ ...unsavedStartStopTimes, [index]: newDurationString });
|
||||
setReduxStartStopTimes({ ...reduxStartStopTimes, [index]: newValue });
|
||||
setUnsavedDuration({ ...unsavedDuration, [index]: newDurationString });
|
||||
setDuration({ ...duration, [index]: newValue });
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,40 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { selectors } from '../../../../../../data/redux';
|
||||
import * as hooks from './hooks';
|
||||
import messages from '../messages';
|
||||
|
||||
jest.mock('react', () => {
|
||||
const updateState = jest.fn();
|
||||
const dispatchFn = jest.fn();
|
||||
return {
|
||||
...jest.requireActual('react'),
|
||||
updateState,
|
||||
useState: jest.fn(val => ([{ state: val }, (newVal) => updateState({ val, newVal })])),
|
||||
useCallback: (cb, prereqs) => ({ useCallback: { cb, prereqs } }),
|
||||
useEffect: jest.fn(),
|
||||
useSelector: jest.fn(),
|
||||
dispatch: dispatchFn,
|
||||
useDispatch: jest.fn(() => dispatchFn),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../data/redux', () => ({
|
||||
actions: {
|
||||
video: {
|
||||
updateField: (val) => ({ updateField: val }),
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
video: {
|
||||
duration: (state) => ({ duration: state }),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
let hook;
|
||||
const dispatch = jest.fn(val => ({ dispatch: val }));
|
||||
const intl = {
|
||||
formatMessage: jest.fn(val => val),
|
||||
};
|
||||
@@ -65,35 +45,35 @@ describe('Video Settings DurationWidget hooks', () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
describe('durationWidget', () => {
|
||||
let reduxStartStopTimes;
|
||||
const duration = {
|
||||
startTime: '00:00:00',
|
||||
stopTime: '00:00:10',
|
||||
};
|
||||
const updateField = jest.fn();
|
||||
beforeEach(() => {
|
||||
hook = hooks.durationWidget({ dispatch });
|
||||
reduxStartStopTimes = useSelector(selectors.video.duration);
|
||||
hook = hooks.durationWidget({ duration, updateField });
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('initialization', () => {
|
||||
test('useEffect memoized on reduxStartStopTimes', () => {
|
||||
hooks.durationWidget({ dispatch });
|
||||
test('useEffect memoized on duration', () => {
|
||||
hooks.durationWidget({ duration, updateField });
|
||||
expect(React.useEffect).toHaveBeenCalled();
|
||||
expect(React.useEffect.mock.calls[0][1]).toEqual([reduxStartStopTimes]);
|
||||
expect(React.useEffect.mock.calls[0][1]).toEqual([duration]);
|
||||
});
|
||||
test('calls setUnsavedStartStopTimes with durationString(reduxStartStopTimes)', () => {
|
||||
hooks.durationWidget({ dispatch });
|
||||
test('calls setUnsavedDuration with durationString(duration)', () => {
|
||||
hooks.durationWidget({ duration, updateField });
|
||||
React.useEffect.mock.calls[0][0]();
|
||||
expect(React.updateState).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('returns', () => {
|
||||
hook = hooks.durationWidget({ dispatch });
|
||||
hook = hooks.durationWidget({ duration, updateField });
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
describe('reduxStartStopTimes, with redux duration value', () => {
|
||||
expect(hook.reduxStartStopTimes).toEqual(useSelector(selectors.video.duration));
|
||||
});
|
||||
describe('unsavedStartStopTimes, defaulted to reduxStartStopTimes', () => {
|
||||
expect(hook.unsavedStartStopTimes).toEqual({ state: hooks.durationString(hook.reduxStartStopTimes) });
|
||||
describe('unsavedDuration, defaulted to duration', () => {
|
||||
expect(hook.unsavedDuration).toEqual({ state: hooks.durationString(duration) });
|
||||
});
|
||||
describe('onBlur, calls updateDuration', () => {
|
||||
jest.spyOn(hooks, 'updateDuration').mockImplementation(jest.fn());
|
||||
@@ -182,78 +162,78 @@ describe('Video Settings DurationWidget hooks', () => {
|
||||
beforeEach(() => {
|
||||
hook = hooks.updateDuration;
|
||||
props = {
|
||||
reduxStartStopTimes: { startTime: 23000, stopTime: 600000 },
|
||||
unsavedStartStopTimes: { startTime: '00:00:23', stopTime: '00:10:00' },
|
||||
setReduxStartStopTimes: jest.fn(),
|
||||
setUnsavedStartStopTimes: jest.fn(),
|
||||
duration: { startTime: 23000, stopTime: 600000 },
|
||||
unsavedDuration: { startTime: '00:00:23', stopTime: '00:10:00' },
|
||||
setDuration: jest.fn(),
|
||||
setUnsavedDuration: jest.fn(),
|
||||
index: 'startTime',
|
||||
inputString: '01:23:45',
|
||||
};
|
||||
});
|
||||
describe('if the passed durationString is valid', () => {
|
||||
it('sets the unsavedStartStopTimes to updated strings and reduxStartStopTimes to new timestamp value', () => {
|
||||
it('sets the unsavedDuration to updated strings and duration to new timestamp value', () => {
|
||||
hook({
|
||||
...props,
|
||||
index: testValidIndex,
|
||||
inputString: testValidDuration,
|
||||
});
|
||||
expect(props.setUnsavedStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.unsavedStartStopTimes,
|
||||
expect(props.setUnsavedDuration).toHaveBeenCalledWith({
|
||||
...props.unsavedDuration,
|
||||
[testValidIndex]: testValidDuration,
|
||||
});
|
||||
expect(props.setReduxStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.reduxStartStopTimes,
|
||||
expect(props.setDuration).toHaveBeenCalledWith({
|
||||
...props.duration,
|
||||
[testValidIndex]: testValidValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('if the passed durationString is not valid', () => {
|
||||
it('updates unsavedStartStopTimes values to 0 (the default)', () => {
|
||||
it('updates unsavedDuration values to 0 (the default)', () => {
|
||||
hook({
|
||||
...props,
|
||||
index: testValidIndex,
|
||||
inputString: testInvalidDuration,
|
||||
});
|
||||
expect(props.setUnsavedStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.unsavedStartStopTimes,
|
||||
expect(props.setUnsavedDuration).toHaveBeenCalledWith({
|
||||
...props.unsavedDuration,
|
||||
[testValidIndex]: testValidDuration,
|
||||
});
|
||||
expect(props.setReduxStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.reduxStartStopTimes,
|
||||
expect(props.setDuration).toHaveBeenCalledWith({
|
||||
...props.duration,
|
||||
[testValidIndex]: testValidValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('if the passed startTime is after (or equal to) the stored non-zero stopTime', () => {
|
||||
it('updates unsavedStartStopTimes startTime values to 1 second before stopTime', () => {
|
||||
it('updates unsavedDuration startTime values to 1 second before stopTime', () => {
|
||||
hook({
|
||||
...props,
|
||||
index: testValidIndex,
|
||||
inputString: '00:10:00',
|
||||
});
|
||||
expect(props.setUnsavedStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.unsavedStartStopTimes,
|
||||
expect(props.setUnsavedDuration).toHaveBeenCalledWith({
|
||||
...props.unsavedDuration,
|
||||
[testValidIndex]: '00:09:59',
|
||||
});
|
||||
expect(props.setReduxStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.reduxStartStopTimes,
|
||||
expect(props.setDuration).toHaveBeenCalledWith({
|
||||
...props.duration,
|
||||
[testValidIndex]: 599000,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('if the passed stopTime is before (or equal to) the stored startTime', () => {
|
||||
it('updates unsavedStartStopTimes stopTime values to 1 second after startTime', () => {
|
||||
it('updates unsavedDuration stopTime values to 1 second after startTime', () => {
|
||||
hook({
|
||||
...props,
|
||||
index: testStopIndex,
|
||||
inputString: '00:00:22',
|
||||
});
|
||||
expect(props.setUnsavedStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.unsavedStartStopTimes,
|
||||
expect(props.setUnsavedDuration).toHaveBeenCalledWith({
|
||||
...props.unsavedDuration,
|
||||
[testStopIndex]: '00:00:24',
|
||||
});
|
||||
expect(props.setReduxStartStopTimes).toHaveBeenCalledWith({
|
||||
...props.reduxStartStopTimes,
|
||||
expect(props.setDuration).toHaveBeenCalledWith({
|
||||
...props.duration,
|
||||
[testStopIndex]: 24000,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Col, Form } from '@edx/paragon';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { actions, selectors } from '../../../../../../data/redux';
|
||||
import { keyStore } from '../../../../../../utils';
|
||||
import CollapsibleFormWidget from '../CollapsibleFormWidget';
|
||||
import hooks from './hooks';
|
||||
@@ -14,28 +16,28 @@ import messages from '../messages';
|
||||
* Also displays the total run time of the video.
|
||||
*/
|
||||
export const DurationWidget = ({
|
||||
// redux
|
||||
duration,
|
||||
updateField,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
reduxStartStopTimes,
|
||||
unsavedStartStopTimes,
|
||||
unsavedDuration,
|
||||
onBlur,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
getTotalLabel,
|
||||
} = hooks.durationWidget({ dispatch });
|
||||
} = hooks.durationWidget({ duration, updateField });
|
||||
|
||||
const timeKeys = keyStore(reduxStartStopTimes);
|
||||
const timeKeys = keyStore(duration);
|
||||
|
||||
return (
|
||||
<CollapsibleFormWidget
|
||||
fontSize="x-small"
|
||||
title={intl.formatMessage(messages.durationTitle)}
|
||||
subtitle={getTotalLabel({
|
||||
duration: reduxStartStopTimes,
|
||||
duration: duration,
|
||||
subtitle: true,
|
||||
intl,
|
||||
})}
|
||||
@@ -48,7 +50,7 @@ export const DurationWidget = ({
|
||||
onBlur={onBlur(timeKeys.startTime)}
|
||||
onChange={onChange(timeKeys.startTime)}
|
||||
onKeyDown={onKeyDown(timeKeys.startTime)}
|
||||
value={unsavedStartStopTimes.startTime}
|
||||
value={unsavedDuration.startTime}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage {...messages.durationHint} />
|
||||
@@ -60,7 +62,7 @@ export const DurationWidget = ({
|
||||
onBlur={onBlur(timeKeys.stopTime)}
|
||||
onChange={onChange(timeKeys.stopTime)}
|
||||
onKeyDown={onKeyDown(timeKeys.stopTime)}
|
||||
value={unsavedStartStopTimes.stopTime}
|
||||
value={unsavedDuration.stopTime}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage {...messages.durationHint} />
|
||||
@@ -69,7 +71,7 @@ export const DurationWidget = ({
|
||||
</Form.Row>
|
||||
<div className="mt-4">
|
||||
{getTotalLabel({
|
||||
duration: reduxStartStopTimes,
|
||||
duration: duration,
|
||||
subtitle: false,
|
||||
intl,
|
||||
})}
|
||||
@@ -79,8 +81,19 @@ export const DurationWidget = ({
|
||||
};
|
||||
|
||||
DurationWidget.propTypes = {
|
||||
// redux
|
||||
duration: PropTypes.objectOf(PropTypes.number).isRequired,
|
||||
updateField: PropTypes.func.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(DurationWidget);
|
||||
export const mapStateToProps = (state) => ({
|
||||
duration: selectors.video.duration(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
updateField: actions.video.updateField,
|
||||
};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(DurationWidget));
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { actions, selectors } from '../../../../../../data/redux';
|
||||
import { formatMessage } from '../../../../../../../testUtils';
|
||||
import { DurationWidget } from '.';
|
||||
import { DurationWidget, mapStateToProps, mapDispatchToProps } from '.';
|
||||
|
||||
jest.mock('../../../../../../data/redux', () => ({
|
||||
actions: {
|
||||
video: {
|
||||
updateField: jest.fn().mockName('actions.video.updateField'),
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
video: {
|
||||
duration: jest.fn(state => ({ duration: state })),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DurationWidget', () => {
|
||||
const props = {
|
||||
isError: false,
|
||||
subtitle: 'SuBTItle',
|
||||
title: 'tiTLE',
|
||||
duration: {
|
||||
startTime: '00:00:00',
|
||||
stopTime: '00:00:10',
|
||||
},
|
||||
updateField: jest.fn().mockName('updateField'),
|
||||
// inject
|
||||
intl: { formatMessage },
|
||||
};
|
||||
@@ -19,4 +35,17 @@ describe('DurationWidget', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
|
||||
test('duration from video.duration', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).duration,
|
||||
).toEqual(selectors.video.duration(testState));
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
test('updateField from actions.video.updateField', () => {
|
||||
expect(mapDispatchToProps.updateField).toEqual(actions.video.updateField);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -286,5 +286,3 @@ export const fetchStudioView = ({ blockId, studioEndpointUrl }) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const checkTranscriptsForImport = () => mockPromise({});
|
||||
|
||||
Reference in New Issue
Block a user