feat: revert to connect

This commit is contained in:
rayzhou-bit
2023-01-18 06:12:49 -05:00
parent e66863795c
commit 34b7b3314c
6 changed files with 130 additions and 114 deletions

View File

@@ -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)>
`;

View File

@@ -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 });
};
/**

View File

@@ -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,
});
});

View File

@@ -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));

View File

@@ -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);
});
});
});

View File

@@ -286,5 +286,3 @@ export const fetchStudioView = ({ blockId, studioEndpointUrl }) => {
},
});
};
export const checkTranscriptsForImport = () => mockPromise({});