Skip to content

Commit

Permalink
refactor(app): consolidate header onClicks into their own hook
Browse files Browse the repository at this point in the history
  • Loading branch information
mjhuff committed Feb 6, 2025
1 parent b0d547b commit 1ac55e6
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 117 deletions.
2 changes: 1 addition & 1 deletion app/src/App/OnDeviceDisplayApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export const OnDeviceDisplayApp = (): JSX.Element => {

// TODO (sb:6/12/23) Create a notification manager to set up preference and order of takeover modals
return (
<ApiHostProvider hostname="10.14.19.53">
<ApiHostProvider hostname="127.0.0.1">
<ReactQueryDevtools />
<InitialLoadingScreen>
<LocalizationProvider>
Expand Down
17 changes: 3 additions & 14 deletions app/src/organisms/LabwarePositionCheck/LPCErrorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,13 @@ const SUPPORT_EMAIL = '[email protected]'

export function LPCErrorModal(props: LPCWizardContentProps): JSX.Element {
const { t } = useTranslation(['labware_position_check', 'shared', 'branded'])
const {
errorMessage,
toggleRobotMoving,
handleCleanUpAndClose,
} = props.commandUtils

// If the maintenance run fails, we cannot move the gantry, so just clean up LPC.
const handleClose = (): void => {
void toggleRobotMoving(true).then(() => {
void handleCleanUpAndClose()
})
}
const { errorMessage, headerCommands } = props.commandUtils

return (
<LPCContentContainer
{...props}
header={t('labware_position_check_title')}
onClickButton={handleClose}
onClickButton={headerCommands.handleClose}
buttonText={t('exit')}
>
<ModalContainer
Expand Down Expand Up @@ -82,7 +71,7 @@ export function LPCErrorModal(props: LPCWizardContentProps): JSX.Element {
<PrimaryButton
textTransform={TEXT_TRANSFORM_CAPITALIZE}
alignSelf={ALIGN_FLEX_END}
onClick={handleClose}
onClick={headerCommands.handleClose}
>
{t('shared:exit')}
</PrimaryButton>
Expand Down
33 changes: 4 additions & 29 deletions app/src/organisms/LabwarePositionCheck/LPCProbeNotAttached.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,27 @@ import { useTranslation } from 'react-i18next'
import { ProbeNotAttached } from '/app/organisms/PipetteWizardFlows/ProbeNotAttached'
import { getIsOnDevice } from '/app/redux/config'
import { LPCContentContainer } from '/app/organisms/LabwarePositionCheck/LPCContentContainer'
import { LPC_STEP, selectActivePipette } from '/app/redux/protocol-runs'

import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'

// TODO(jh, 02-05-25): EXEC-1190.
export function LPCProbeNotAttached(props: LPCWizardContentProps): JSX.Element {
const { t } = useTranslation('labware_position_check')
const { commandUtils, proceedStep, runId } = props
const {
setShowUnableToDetect,
toggleRobotMoving,
handleValidMoveToMaintenancePosition,
handleProbeAttachment,
} = commandUtils
const { commandUtils } = props
const { setShowUnableToDetect, headerCommands } = commandUtils
const isOnDevice = useSelector(getIsOnDevice)
const pipette = useSelector(selectActivePipette(runId))

const handleAttachProbeCheck = (): void => {
void toggleRobotMoving(true)
.then(() => handleProbeAttachment(pipette, proceedStep))
.then(() => {
proceedStep()
})
.finally(() => toggleRobotMoving(false))
}

const handleNavToDetachProbe = (): void => {
void toggleRobotMoving(true)
.then(() => handleValidMoveToMaintenancePosition(pipette))
.then(() => {
proceedStep(LPC_STEP.DETACH_PROBE)
})
.finally(() => toggleRobotMoving(false))
}

return (
<LPCContentContainer
{...props}
header={t('labware_position_check_title')}
buttonText={t('try_again')}
onClickButton={handleAttachProbeCheck}
onClickButton={headerCommands.handleAttachProbeCheck}
secondaryButtonProps={{
buttonText: t('exit'),
buttonCategory: 'rounded',
buttonType: 'tertiaryLowLight',
onClick: handleNavToDetachProbe,
onClick: headerCommands.handleNavToDetachProbe,
}}
>
<ProbeNotAttached
Expand Down
10 changes: 9 additions & 1 deletion app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { getIsOnDevice } from '/app/redux/config'
import type { LPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows'
import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'
import type { LPCStep } from '/app/redux/protocol-runs'
import { useLPCHeaderCommands } from '/app/organisms/LabwarePositionCheck/hooks/useLPCCommands/useLPCHeaderCommands'

export interface LPCWizardFlexProps extends Omit<LPCFlowsProps, 'robotType'> {}

Expand Down Expand Up @@ -60,12 +61,19 @@ export function LPCWizardFlex(props: LPCWizardFlexProps): JSX.Element {
}
}, [])

const headerCommands = useLPCHeaderCommands({
...props,
LPCHandlerUtils,
proceedStep,
goBackLastStep,
})

return (
<LPCWizardFlexComponent
{...props}
proceedStep={proceedStep}
goBackLastStep={goBackLastStep}
commandUtils={LPCHandlerUtils}
commandUtils={{ ...LPCHandlerUtils, headerCommands }}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { describe, beforeEach, afterEach, it, vi, expect } from 'vitest'
import { renderHook, act, waitFor } from '@testing-library/react'
import { useSelector, Provider } from 'react-redux'
import { createStore } from 'redux'
import { I18nextProvider } from 'react-i18next'

import { i18n } from '/app/i18n'
import { LPC_STEP } from '/app/redux/protocol-runs'
import { useLPCHeaderCommands } from '../useLPCHeaderCommands'

import type { FunctionComponent, ReactNode } from 'react'
import type { UseLPCCommandsResult } from '/app/organisms/LabwarePositionCheck/hooks'
import type { UseLPCHeaderCommandsProps } from '../useLPCHeaderCommands'
import type { State } from '/app/redux/types'
import type { Store } from 'redux'

vi.mock('react-redux', async importOriginal => {
const actual = await importOriginal<typeof Provider>()
return {
...actual,
useSelector: vi.fn(),
}
})

let props: UseLPCHeaderCommandsProps
let mockLPCHandlerUtils: UseLPCCommandsResult
let wrapper: FunctionComponent<{ children: ReactNode }>
let store: Store<State>
let toggleRobotMovingPromise: Promise<void>
let handleStartLPCPromise: Promise<void>
let handleProbeAttachmentPromise: Promise<void>
let handleValidMoveToMaintenancePositionPromise: Promise<void>
let handleCleanUpAndClosePromise: Promise<void>

describe('useLPCHeaderCommands', () => {
const mockPipette = { id: 'mock-pipette' }
const mockRunId = 'mock-run-id'
const mockProceedStep = vi.fn()

beforeEach(() => {
toggleRobotMovingPromise = Promise.resolve()
handleStartLPCPromise = Promise.resolve()
handleProbeAttachmentPromise = Promise.resolve()
handleValidMoveToMaintenancePositionPromise = Promise.resolve()
handleCleanUpAndClosePromise = Promise.resolve()

mockLPCHandlerUtils = {
toggleRobotMoving: vi.fn(() => toggleRobotMovingPromise),
handleStartLPC: vi.fn(() => handleStartLPCPromise),
handleProbeAttachment: vi.fn(() => handleProbeAttachmentPromise),
handleValidMoveToMaintenancePosition: vi.fn(
() => handleValidMoveToMaintenancePositionPromise
),
handleCleanUpAndClose: vi.fn(() => handleCleanUpAndClosePromise),
} as any

props = {
LPCHandlerUtils: mockLPCHandlerUtils,
proceedStep: mockProceedStep,
goBackLastStep: vi.fn(),
runId: mockRunId,
}

store = createStore(vi.fn(), {})
store.dispatch = vi.fn()

wrapper = ({ children }) => (
<I18nextProvider i18n={i18n}>
<Provider store={store}>{children}</Provider>
</I18nextProvider>
)

vi.mocked(useSelector).mockImplementation(() => {
return mockPipette
})
})

afterEach(() => {
vi.clearAllMocks()
})

it('should execute handleProceed commands in correct sequence', async () => {
const { result } = renderHook(() => useLPCHeaderCommands(props), {
wrapper,
})

await act(async () => {
result.current.handleProceed()
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
})

await waitFor(() => {
expect(mockLPCHandlerUtils.handleStartLPC).toHaveBeenCalledWith(
mockPipette,
mockProceedStep
)
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
})
})

it('should execute handleAttachProbeCheck commands in correct sequence', async () => {
const { result } = renderHook(() => useLPCHeaderCommands(props), {
wrapper,
})

await act(async () => {
result.current.handleAttachProbeCheck()
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
})

await waitFor(() => {
expect(mockLPCHandlerUtils.handleProbeAttachment).toHaveBeenCalledWith(
mockPipette,
mockProceedStep
)
})

await waitFor(() => {
expect(mockProceedStep).toHaveBeenCalled()
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
})
})

it('should execute handleNavToDetachProbe commands in correct sequence', async () => {
const { result } = renderHook(() => useLPCHeaderCommands(props), {
wrapper,
})

await act(async () => {
result.current.handleNavToDetachProbe()
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
})

await waitFor(() => {
expect(
mockLPCHandlerUtils.handleValidMoveToMaintenancePosition
).toHaveBeenCalledWith(mockPipette)
})

await waitFor(() => {
expect(mockProceedStep).toHaveBeenCalledWith(LPC_STEP.DETACH_PROBE)
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
})
})

it('should execute handleClose commands in correct sequence', async () => {
const { result } = renderHook(() => useLPCHeaderCommands(props), {
wrapper,
})

await act(async () => {
result.current.handleClose()
})

await waitFor(() => {
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
})

await waitFor(() => {
expect(mockLPCHandlerUtils.handleCleanUpAndClose).toHaveBeenCalled()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useSelector } from 'react-redux'

import { LPC_STEP, selectActivePipette } from '/app/redux/protocol-runs'

import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'
import type { UseLPCCommandsResult } from '/app/organisms/LabwarePositionCheck/hooks'

export type UseLPCHeaderCommandsProps = Omit<
LPCWizardContentProps,
'commandUtils'
> & {
LPCHandlerUtils: UseLPCCommandsResult
}

export interface UseLPCHeaderCommandsResult {
handleProceed: () => void
handleAttachProbeCheck: () => void
handleNavToDetachProbe: () => void
handleClose: () => void
}

// Wraps core LPC command functionality, since the header component reuses many of the same commands.
export function useLPCHeaderCommands({
LPCHandlerUtils,
proceedStep,
runId,
}: UseLPCHeaderCommandsProps): UseLPCHeaderCommandsResult {
const activePipette = useSelector(selectActivePipette(runId))
const pipette = useSelector(selectActivePipette(runId))

const {
handleStartLPC,
toggleRobotMoving,
handleValidMoveToMaintenancePosition,
handleCleanUpAndClose,
handleProbeAttachment,
} = LPCHandlerUtils

const handleProceed = (): void => {
void toggleRobotMoving(true)
.then(() => handleStartLPC(activePipette, proceedStep))
.finally(() => toggleRobotMoving(false))
}

// If the maintenance run fails, we cannot move the gantry, so just clean up LPC.
const handleClose = (): void => {
void toggleRobotMoving(true).then(() => {
void handleCleanUpAndClose()
})
}

const handleAttachProbeCheck = (): void => {
void toggleRobotMoving(true)
.then(() => handleProbeAttachment(pipette, proceedStep))
.then(() => {
proceedStep()
})
.finally(() => toggleRobotMoving(false))
}

const handleNavToDetachProbe = (): void => {
void toggleRobotMoving(true)
.then(() => handleValidMoveToMaintenancePosition(pipette))
.then(() => {
proceedStep(LPC_STEP.DETACH_PROBE)
})
.finally(() => toggleRobotMoving(false))
}

return {
handleProceed,
handleAttachProbeCheck,
handleNavToDetachProbe,
handleClose,
}
}
Loading

0 comments on commit 1ac55e6

Please sign in to comment.