Skip to content

Commit

Permalink
refactor(protocol-designer): remove usage of class components and con…
Browse files Browse the repository at this point in the history
…nect fns

closes RAUT-935
  • Loading branch information
jerader committed Jan 19, 2024
1 parent 4946748 commit 6babdfe
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const NameThisLabware = (props: Props): JSX.Element => {
}

const saveNickname = (): void => {
setLabwareName()
setLabwareName(inputValue ?? null)
}
const wrapperRef: React.RefObject<HTMLDivElement> = useOnClickOutside({
onClickOutside: saveNickname,
Expand Down
106 changes: 45 additions & 61 deletions protocol-designer/src/components/EditableTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,80 +9,64 @@ interface Props {
saveEdit: (newValue: string) => unknown
}

interface State {
editing: boolean
transientValue?: string | null
}
export function EditableTextField(props: Props): JSX.Element {
const { className, value, saveEdit } = props
const [editing, setEditing] = React.useState<boolean>(false)
const [transientValue, setTransientValue] = React.useState(value)

Check warning on line 15 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L13-L15

Added lines #L13 - L15 were not covered by tests

export class EditableTextField extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
editing: false,
transientValue: this.props.value,
}
const enterEditMode = (): void => {
setEditing(true)
setTransientValue(value)

Check warning on line 19 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L17-L19

Added lines #L17 - L19 were not covered by tests
}

enterEditMode: () => void = () =>
this.setState({ editing: true, transientValue: this.props.value })

handleCancel: () => void = () => {
this.setState({
editing: false,
transientValue: this.props.value,
})
const handleCancel = (): void => {
setEditing(false)
setTransientValue(value)

Check warning on line 23 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L21-L23

Added lines #L21 - L23 were not covered by tests
}

handleKeyUp: (e: React.KeyboardEvent) => void = e => {
const handleKeyUp = (e: React.KeyboardEvent): void => {

Check warning on line 26 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L26

Added line #L26 was not covered by tests
if (e.key === 'Escape') {
this.handleCancel()
handleCancel()

Check warning on line 28 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L28

Added line #L28 was not covered by tests
}
}

handleFormSubmit: (e: React.FormEvent) => void = e => {
e.preventDefault() // avoid 'form is not connected' warning
this.handleSubmit()
const handleSubmit = (): void => {
setEditing(false)

Check warning on line 33 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L32-L33

Added lines #L32 - L33 were not covered by tests
saveEdit(transientValue ?? '')
}

handleSubmit: () => void = () => {
this.setState({ editing: false }, () =>
this.props.saveEdit(this.state.transientValue || '')
)
const handleFormSubmit = (e: React.FormEvent): void => {
e.preventDefault() // avoid 'form is not connected' warning
handleSubmit()

Check warning on line 38 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L36-L38

Added lines #L36 - L38 were not covered by tests
}

updateValue: (e: React.ChangeEvent<HTMLInputElement>) => void = e => {
this.setState({ transientValue: e.currentTarget.value })
const updateValue = (e: React.ChangeEvent<HTMLInputElement>): void => {
setTransientValue(e.currentTarget.value)

Check warning on line 42 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L41-L42

Added lines #L41 - L42 were not covered by tests
}

render(): React.ReactNode {
const { className, value } = this.props
if (this.state.editing) {
return (
<ClickOutside onClickOutside={this.handleSubmit}>
{({ ref }) => (
<form
className={className}
onKeyUp={this.handleKeyUp}
onSubmit={this.handleFormSubmit}
ref={ref}
>
<InputField
autoFocus
value={this.state.transientValue}
onChange={this.updateValue}
units={<Icon name="pencil" className={styles.edit_icon} />}
/>
</form>
)}
</ClickOutside>
)
}

if (editing) {
return (
<div onClick={this.enterEditMode} className={className}>
<div className={styles.static_value}>{value}</div>
<Icon name="pencil" className={styles.edit_icon_right} />
</div>
<ClickOutside onClickOutside={handleSubmit}>
{({ ref }) => (
<form

Check warning on line 48 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L48

Added line #L48 was not covered by tests
className={className}
onKeyUp={handleKeyUp}
onSubmit={handleFormSubmit}
ref={ref}
>
<InputField
autoFocus
value={transientValue}
onChange={updateValue}
units={<Icon name="pencil" className={styles.edit_icon} />}
/>
</form>
)}
</ClickOutside>
)
}

return (

Check warning on line 66 in protocol-designer/src/components/EditableTextField.tsx

View check run for this annotation

Codecov / codecov/patch

protocol-designer/src/components/EditableTextField.tsx#L66

Added line #L66 was not covered by tests
<div onClick={enterEditMode} className={className}>
<div className={styles.static_value}>{value}</div>
<Icon name="pencil" className={styles.edit_icon_right} />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import * as React from 'react'
import i18next from 'i18next'
import { fireEvent, screen } from '@testing-library/react'
import { renderWithProviders, nestedTextMatcher } from '@opentrons/components'
import {
getIsLabwareAboveHeight,
MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM,
} from '@opentrons/shared-data'
import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors'
import {
ADAPTER_96_CHANNEL,
getLabwareCompatibleWithAdapter,
} from '../../../utils/labwareModuleCompatibility'
import { i18n } from '../../../localization'
import { LabwareSelectionModal } from '../LabwareSelectionModal'
import {
getInitialDeckSetup,
getPermittedTipracks,
getPipetteEntities,
} from '../../../step-forms/selectors'
import { getHas96Channel } from '../../../utils'
import { getCustomLabwareDefsByURI } from '../../../labware-defs/selectors'

jest.mock('../../../utils/labwareModuleCompatibility')
jest.mock('../../../step-forms/selectors')
jest.mock('../../../labware-defs/selectors')
jest.mock('../../Hints/useBlockingHint')
jest.mock('../../../utils')
jest.mock('../../../labware-ingred/selectors')
jest.mock('@opentrons/shared-data', () => {
const actualSharedData = jest.requireActual('@opentrons/shared-data')
return {
Expand All @@ -28,47 +40,98 @@ const mockGetIsLabwareAboveHeight = getIsLabwareAboveHeight as jest.MockedFuncti
const mockGetLabwareCompatibleWithAdapter = getLabwareCompatibleWithAdapter as jest.MockedFunction<
typeof getLabwareCompatibleWithAdapter
>
const render = (props: React.ComponentProps<typeof LabwareSelectionModal>) => {
return renderWithProviders(<LabwareSelectionModal {...props} />, {
i18nInstance: i18next,
const mockGetInitialDeckSetup = getInitialDeckSetup as jest.MockedFunction<
typeof getInitialDeckSetup
>
const mockSlot = labwareIngredSelectors.selectedAddLabwareSlot as jest.MockedFunction<
typeof labwareIngredSelectors.selectedAddLabwareSlot
>
const mockGetHas96Channel = getHas96Channel as jest.MockedFunction<
typeof getHas96Channel
>
const mockGetPipetteEntities = getPipetteEntities as jest.MockedFunction<
typeof getPipetteEntities
>
const mockGetPermittedTipracks = getPermittedTipracks as jest.MockedFunction<
typeof getPermittedTipracks
>
const mockGetCustomLabwareDefsByURI = getCustomLabwareDefsByURI as jest.MockedFunction<
typeof getCustomLabwareDefsByURI
>
const render = () => {
return renderWithProviders(<LabwareSelectionModal />, {
i18nInstance: i18n,
})[0]
}

const mockTipUri = 'fixture/fixture_tiprack_1000_ul/1'
const mockPermittedTipracks = [mockTipUri]

describe('LabwareSelectionModal', () => {
let props: React.ComponentProps<typeof LabwareSelectionModal>
beforeEach(() => {
props = {
onClose: jest.fn(),
onUploadLabware: jest.fn(),
selectLabware: jest.fn(),
customLabwareDefs: {},
permittedTipracks: [],
isNextToHeaterShaker: false,
has96Channel: false,
}
mockGetLabwareCompatibleWithAdapter.mockReturnValue([])
mockGetInitialDeckSetup.mockReturnValue({
labware: {},
modules: {},
pipettes: {},
additionalEquipmentOnDeck: {},
})
mockSlot.mockReturnValue('2')
mockGetHas96Channel.mockReturnValue(false)
mockGetPermittedTipracks.mockReturnValue(mockPermittedTipracks)
mockGetPipetteEntities.mockReturnValue({
mockPip: {
tiprackLabwareDef: {} as any,
spec: {} as any,
name: 'p1000_single',
id: 'mockId',
tiprackDefURI: mockTipUri,
},
})
mockGetCustomLabwareDefsByURI.mockReturnValue({})
})
it('should NOT filter out labware above 57 mm when the slot is NOT next to a heater shaker', () => {
props.isNextToHeaterShaker = false
render(props)
render()
expect(mockGetIsLabwareAboveHeight).not.toHaveBeenCalled()
})
it('should filter out labware above 57 mm when the slot is next to a heater shaker', () => {
props.isNextToHeaterShaker = true
render(props)
mockGetInitialDeckSetup.mockReturnValue({
labware: {},
modules: {
heaterShaker: {
id: 'mockId',
type: 'heaterShakerModuleType',
model: 'heaterShakerModuleV1',
moduleState: {} as any,
slot: '1',
} as any,
},
pipettes: {},
additionalEquipmentOnDeck: {},
})
render()
expect(mockGetIsLabwareAboveHeight).toHaveBeenCalledWith(
expect.any(Object),
MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM
)
})
it('should display only permitted tipracks if the 96-channel is attached', () => {
const mockTipUri = 'fixture/fixture_tiprack_1000_ul/1'
const mockPermittedTipracks = [mockTipUri]
props.slot = 'A2'
props.has96Channel = true
props.adapterLoadName = ADAPTER_96_CHANNEL
props.permittedTipracks = mockPermittedTipracks
render(props)
mockGetHas96Channel.mockReturnValue(true)
mockSlot.mockReturnValue('adapter')
mockGetInitialDeckSetup.mockReturnValue({
labware: {
adapter: {
id: 'adapter',
labwareDefURI: `opentrons/${ADAPTER_96_CHANNEL}/1`,
slot: 'A2',
def: { parameters: { loadName: ADAPTER_96_CHANNEL } } as any,
},
},
modules: {},
pipettes: {},
additionalEquipmentOnDeck: {},
})
render()
fireEvent.click(
screen.getByText(nestedTextMatcher('adapter compatible labware'))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,11 @@ import styles from './TipPositionInput.css'

import type { FieldProps } from '../../types'

interface OP extends FieldProps {
interface Props extends FieldProps {
labwareId?: string | null
className?: string
}

interface SP {
mmFromBottom: number | null
wellDepthMm: number
}

type Props = OP & SP

export function TipPositionField(props: Props): JSX.Element {
const {
disabled,
Expand Down Expand Up @@ -64,7 +57,7 @@ export function TipPositionField(props: Props): JSX.Element {
}

const handleOpen = (): void => {
if (props.wellDepthMm) {
if (wellDepthMm) {
setModalOpen(true)
}
}
Expand Down
Loading

0 comments on commit 6babdfe

Please sign in to comment.