diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.js b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.js index d7695e57a61..eccf55c0fcc 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.js +++ b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.js @@ -1,8 +1,6 @@ // @flow import * as React from 'react' import cx from 'classnames' -import { connect } from 'react-redux' - import { i18n } from '../../../../localization' import { Portal } from '../../../portals/MainPageModalPortal' import { @@ -13,10 +11,7 @@ import { DropdownField, } from '@opentrons/components' import modalStyles from '../../../modals/modal.css' -import { actions } from '../../../../steplist' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import type { BaseState, ThunkDispatch } from '../../../../types' -import type { WellOrderOption } from '../../../../form-types' +import type { WellOrderOption, FormData } from '../../../../form-types' import { WellOrderViz } from './WellOrderViz' import styles from './WellOrderInput.css' @@ -30,66 +25,76 @@ const WELL_ORDER_VALUES: Array = [ ...VERTICAL_VALUES, ...HORIZONTAL_VALUES, ] - -type SP = {| - initialFirstValue: ?WellOrderOption, - initialSecondValue: ?WellOrderOption, -|} - -type DP = {| - updateValues: ( - firstValue: ?WellOrderOption, - secondValue: ?WellOrderOption - ) => mixed, -|} - -type OP = {| +type Props = {| isOpen: boolean, closeModal: () => mixed, prefix: 'aspirate' | 'dispense' | 'mix', + formData: FormData, + updateValues: ( + firstValue: ?WellOrderOption, + secondValue: ?WellOrderOption + ) => void, |} -type Props = {| ...OP, ...SP, ...DP |} - type State = { firstValue: ?WellOrderOption, secondValue: ?WellOrderOption, } -class WellOrderModalComponent extends React.Component { +export class WellOrderModal extends React.Component { constructor(props: Props) { super(props) + const { + initialFirstValue, + initialSecondValue, + } = this.getInitialFirstValues() this.state = { - firstValue: props.initialFirstValue, - secondValue: props.initialSecondValue, + firstValue: initialFirstValue, + secondValue: initialSecondValue, + } + } + + getInitialFirstValues: () => {| + initialFirstValue: ?WellOrderOption, + initialSecondValue: ?WellOrderOption, + |} = () => { + const { formData, prefix } = this.props + return { + initialFirstValue: formData && formData[`${prefix}_wellOrder_first`], + initialSecondValue: formData && formData[`${prefix}_wellOrder_second`], } } - applyChanges = () => { + applyChanges: () => void = () => { this.props.updateValues(this.state.firstValue, this.state.secondValue) } - handleReset = () => { + handleReset: () => void = () => { this.setState( { firstValue: DEFAULT_FIRST, secondValue: DEFAULT_SECOND }, this.applyChanges ) this.props.closeModal() } - handleCancel = () => { - const { initialFirstValue, initialSecondValue } = this.props + handleCancel: () => void = () => { + const { + initialFirstValue, + initialSecondValue, + } = this.getInitialFirstValues() this.setState( { firstValue: initialFirstValue, secondValue: initialSecondValue }, this.applyChanges ) this.props.closeModal() } - handleDone = () => { + handleDone: () => void = () => { this.applyChanges() this.props.closeModal() } - makeOnChange = (ordinality: 'first' | 'second') => ( - e: SyntheticEvent - ) => { - const { value } = e.currentTarget + makeOnChange: ( + ordinality: 'first' | 'second' + ) => ( + event: SyntheticEvent + ) => void = ordinality => event => { + const { value } = event.currentTarget let nextState = { [`${ordinality}Value`]: value } if (ordinality === 'first') { if ( @@ -106,14 +111,18 @@ class WellOrderModalComponent extends React.Component { } this.setState(nextState) } - isSecondOptionDisabled = (value: WellOrderOption) => { + isSecondOptionDisabled: WellOrderOption => boolean = ( + value: WellOrderOption + ) => { if (VERTICAL_VALUES.includes(this.state.firstValue)) { return VERTICAL_VALUES.includes(value) } else if (HORIZONTAL_VALUES.includes(this.state.firstValue)) { return HORIZONTAL_VALUES.includes(value) + } else { + return false } } - render() { + render(): React.Node { if (!this.props.isOpen) return null const { firstValue, secondValue } = this.state return ( @@ -202,38 +211,3 @@ class WellOrderModalComponent extends React.Component { ) } } - -const mapSTP = (state: BaseState, ownProps: OP): SP => { - const formData = stepFormSelectors.getUnsavedForm(state) - return { - initialFirstValue: - formData && formData[`${ownProps.prefix}_wellOrder_first`], - initialSecondValue: - formData && formData[`${ownProps.prefix}_wellOrder_second`], - } -} - -const mapDTP = (dispatch: ThunkDispatch<*>, ownProps: OP): DP => ({ - updateValues: (firstValue, secondValue) => { - dispatch( - actions.changeFormInput({ - update: { - [`${ownProps.prefix}_wellOrder_first`]: firstValue, - [`${ownProps.prefix}_wellOrder_second`]: secondValue, - }, - }) - ) - }, -}) - -export const WellOrderModal: React.AbstractComponent = connect< - Props, - OP, - SP, - DP, - _, - _ ->( - mapSTP, - mapDTP -)(WellOrderModalComponent) diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.js b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.js index d7baef24e87..de51ea9a73b 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.js +++ b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.js @@ -1,29 +1,26 @@ // @flow import * as React from 'react' -import { connect } from 'react-redux' import { FormGroup, Tooltip, useHoverTooltip } from '@opentrons/components' import cx from 'classnames' import { i18n } from '../../../../localization' -import { selectors as stepFormSelectors } from '../../../../step-forms' import ZIG_ZAG_IMAGE from '../../../../images/zig_zag_icon.svg' import { WellOrderModal } from './WellOrderModal' - -import type { BaseState } from '../../../../types' - import stepEditStyles from '../../StepEditForm.css' import styles from './WellOrderInput.css' +import type { FormData } from '../../../../form-types' +import type { FieldProps } from '../../types' -type OP = {| +type Props = {| className?: ?string, label?: string, prefix: 'aspirate' | 'dispense' | 'mix', + formData: FormData, + updateFirstWellOrder: $PropertyType, + updateSecondWellOrder: $PropertyType, |} -type SP = {| iconClassNames: Array |} - -type Props = { ...OP, ...SP } - -function WellOrderInput(props: Props) { +export const WellOrderField = (props: Props): React.Node => { + const { formData, updateFirstWellOrder, updateSecondWellOrder } = props const [isModalOpen, setModalOpen] = React.useState(false) const handleOpen = () => { @@ -33,6 +30,21 @@ function WellOrderInput(props: Props) { setModalOpen(false) } + const updateValues = (firstValue, secondValue) => { + updateFirstWellOrder(firstValue) + updateSecondWellOrder(secondValue) + } + + const getIconClassNames = () => { + let iconClassNames = [] + if (formData) { + const first = formData[`${props.prefix}_wellOrder_first`] + const second = formData[`${props.prefix}_wellOrder_second`] + iconClassNames = [styles[`${first}_first`], styles[`${second}_second`]] + } + return iconClassNames + } + const [targetProps, tooltipProps] = useHoverTooltip() const className = cx(props.className, { @@ -51,6 +63,8 @@ function WellOrderInput(props: Props) { prefix={props.prefix} closeModal={handleClose} isOpen={isModalOpen} + formData={formData} + updateValues={updateValues} /> @@ -66,24 +80,3 @@ function WellOrderInput(props: Props) { ) } - -const mapSTP = (state: BaseState, ownProps: OP): SP => { - const formData = stepFormSelectors.getUnsavedForm(state) - - let iconClassNames = [] - if (formData) { - const first = formData[`${ownProps.prefix}_wellOrder_first`] - const second = formData[`${ownProps.prefix}_wellOrder_second`] - iconClassNames = [styles[`${first}_first`], styles[`${second}_second`]] - } - return { iconClassNames } -} - -export const WellOrderField: React.AbstractComponent = connect< - Props, - OP, - SP, - _, - _, - _ ->(mapSTP)(WellOrderInput) diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.js b/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.js new file mode 100644 index 00000000000..6f605401c1a --- /dev/null +++ b/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.js @@ -0,0 +1,32 @@ +// @flow +import React from 'react' +import { mount } from 'enzyme' +import { act } from 'react-dom/test-utils' +import { WellOrderField } from '../WellOrderField' +import { WellOrderModal } from '../WellOrderField/WellOrderModal' + +describe('WellOrderField', () => { + const render = _props => mount() + + let props + beforeEach(() => { + props = { + prefix: 'aspirate', + formData: ({}: any), + updateFirstWellOrder: jest.fn(), + updateSecondWellOrder: jest.fn(), + } + }) + + describe('WellOrderModal', () => { + it('should call correct updater fns passed in', () => { + const wrapper = render(props) + const wellOrderModal = wrapper.find(WellOrderModal) + act(() => { + wellOrderModal.prop('updateValues')('l2r', 't2b') + }) + expect(props.updateFirstWellOrder).toHaveBeenCalledWith('l2r') + expect(props.updateSecondWellOrder).toHaveBeenCalledWith('t2b') + }) + }) +}) diff --git a/protocol-designer/src/components/StepEditForm/forms/MixForm.js b/protocol-designer/src/components/StepEditForm/forms/MixForm.js index 9f438f20fea..23e9226fa29 100644 --- a/protocol-designer/src/components/StepEditForm/forms/MixForm.js +++ b/protocol-designer/src/components/StepEditForm/forms/MixForm.js @@ -103,8 +103,15 @@ export const MixForm = (props: StepFormProps): React.Node => { formData={formData} /> { diff --git a/protocol-designer/src/components/StepEditForm/forms/__tests__/MixForm.test.js b/protocol-designer/src/components/StepEditForm/forms/__tests__/MixForm.test.js index 35afa2b69af..fc7c28f37ef 100644 --- a/protocol-designer/src/components/StepEditForm/forms/__tests__/MixForm.test.js +++ b/protocol-designer/src/components/StepEditForm/forms/__tests__/MixForm.test.js @@ -5,6 +5,7 @@ import { Provider } from 'react-redux' import { MixForm } from '../MixForm' import { AspDispSection } from '../AspDispSection' import * as stepFormSelectors from '../../../../step-forms/selectors' +import { WellOrderField } from '../../fields' import type { BaseState } from '../../../../types' const { DelayFields } = jest.requireActual('../../fields') @@ -78,6 +79,24 @@ describe('MixForm', () => { updateValue: (jest.fn(): any), value: null, }, + mix_wellOrder_first: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'mix_wellOrder_first', + updateValue: (jest.fn(): any), + value: null, + }, + mix_wellOrder_second: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'mix_wellOrder_second', + updateValue: (jest.fn(): any), + value: null, + }, }, } }) @@ -94,7 +113,7 @@ describe('MixForm', () => { }) describe('when advanced settings are visible', () => { - it('should render the aspirate delay fields when advanced settings are visible', () => { + it('should render the aspirate delay fields', () => { const wrapper = render(props) showAdvancedSettings(wrapper) @@ -126,5 +145,19 @@ describe('MixForm', () => { // no tip position field expect(aspirateDelayFields.prop('tipPositionFieldName')).toBe(undefined) }) + it('should render the mix well order field', () => { + const wrapper = render(props) + showAdvancedSettings(wrapper) + const wellOrderField = wrapper.find(WellOrderField) + expect(wellOrderField.props()).toMatchObject({ + prefix: 'mix', + label: 'Well order', + formData: props.formData, + updateFirstWellOrder: + props.propsForFields['mix_wellOrder_first'].updateValue, + updateSecondWellOrder: + props.propsForFields['mix_wellOrder_second'].updateValue, + }) + }) }) }) diff --git a/protocol-designer/src/components/StepEditForm/forms/__tests__/SourceDestFields.test.js b/protocol-designer/src/components/StepEditForm/forms/__tests__/SourceDestFields.test.js index 6b73149742f..2900dbaef25 100644 --- a/protocol-designer/src/components/StepEditForm/forms/__tests__/SourceDestFields.test.js +++ b/protocol-designer/src/components/StepEditForm/forms/__tests__/SourceDestFields.test.js @@ -3,7 +3,7 @@ import React from 'react' import { Provider } from 'react-redux' import { mount } from 'enzyme' import { selectors as stepFormSelectors } from '../../../../step-forms' -import { CheckboxRowField, DelayFields } from '../../fields' +import { CheckboxRowField, DelayFields, WellOrderField } from '../../fields' import { SourceDestFields } from '../MoveLiquidForm/SourceDestFields' import type { BaseState } from '../../../../types' @@ -76,6 +76,24 @@ describe('SourceDestFields', () => { updateValue: (jest.fn(): any), value: true, }, + aspirate_wellOrder_first: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'aspirate_wellOrder_first', + updateValue: (jest.fn(): any), + value: true, + }, + aspirate_wellOrder_second: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'aspirate_wellOrder_second', + updateValue: (jest.fn(): any), + value: true, + }, dispense_airGap_checkbox: { onFieldFocus: (jest.fn(): any), onFieldBlur: (jest.fn(): any), @@ -112,6 +130,24 @@ describe('SourceDestFields', () => { updateValue: (jest.fn(): any), value: true, }, + dispense_wellOrder_first: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'dispense_wellOrder_first', + updateValue: (jest.fn(): any), + value: true, + }, + dispense_wellOrder_second: { + onFieldFocus: (jest.fn(): any), + onFieldBlur: (jest.fn(): any), + errorToShow: null, + disabled: false, + name: 'dispense_wellOrder_second', + updateValue: (jest.fn(): any), + value: true, + }, blowout_checkbox: { onFieldFocus: (jest.fn(): any), onFieldBlur: (jest.fn(): any), @@ -167,10 +203,26 @@ describe('SourceDestFields', () => { expect(checkboxes.at(2).prop('name')).toBe('aspirate_touchTip_checkbox') expect(checkboxes.at(3).prop('name')).toBe('aspirate_airGap_checkbox') }) + it('should render a well order field', () => { + const wrapper = render(props) + const wellOrderField = wrapper.find(WellOrderField) + + expect(wellOrderField.props()).toMatchObject({ + prefix: 'aspirate', + label: 'Well order', + formData: props.formData, + updateFirstWellOrder: + props.propsForFields['aspirate_wellOrder_first'].updateValue, + updateSecondWellOrder: + props.propsForFields['aspirate_wellOrder_second'].updateValue, + }) + }) }) describe('Dispense section', () => { + beforeEach(() => { + props = { ...props, prefix: 'dispense' } + }) it('should render the correct checkboxes', () => { - props.prefix = 'dispense' const wrapper = render(props) const checkboxes = wrapper.find(CheckboxRowField) @@ -185,5 +237,19 @@ describe('SourceDestFields', () => { expect(checkboxes.at(2).prop('name')).toBe('blowout_checkbox') expect(checkboxes.at(3).prop('name')).toBe('dispense_airGap_checkbox') }) + it('should render a well order field', () => { + const wrapper = render(props) + const wellOrderField = wrapper.find(WellOrderField) + + expect(wellOrderField.props()).toMatchObject({ + prefix: 'dispense', + label: 'Well order', + formData: props.formData, + updateFirstWellOrder: + props.propsForFields['dispense_wellOrder_first'].updateValue, + updateSecondWellOrder: + props.propsForFields['dispense_wellOrder_second'].updateValue, + }) + }) }) })