Skip to content

Commit 1ac55e6

Browse files
committed
refactor(app): consolidate header onClicks into their own hook
1 parent b0d547b commit 1ac55e6

File tree

11 files changed

+286
-117
lines changed

11 files changed

+286
-117
lines changed

app/src/App/OnDeviceDisplayApp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const OnDeviceDisplayApp = (): JSX.Element => {
182182

183183
// TODO (sb:6/12/23) Create a notification manager to set up preference and order of takeover modals
184184
return (
185-
<ApiHostProvider hostname="10.14.19.53">
185+
<ApiHostProvider hostname="127.0.0.1">
186186
<ReactQueryDevtools />
187187
<InitialLoadingScreen>
188188
<LocalizationProvider>

app/src/organisms/LabwarePositionCheck/LPCErrorModal.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,13 @@ const SUPPORT_EMAIL = '[email protected]'
2828

2929
export function LPCErrorModal(props: LPCWizardContentProps): JSX.Element {
3030
const { t } = useTranslation(['labware_position_check', 'shared', 'branded'])
31-
const {
32-
errorMessage,
33-
toggleRobotMoving,
34-
handleCleanUpAndClose,
35-
} = props.commandUtils
36-
37-
// If the maintenance run fails, we cannot move the gantry, so just clean up LPC.
38-
const handleClose = (): void => {
39-
void toggleRobotMoving(true).then(() => {
40-
void handleCleanUpAndClose()
41-
})
42-
}
31+
const { errorMessage, headerCommands } = props.commandUtils
4332

4433
return (
4534
<LPCContentContainer
4635
{...props}
4736
header={t('labware_position_check_title')}
48-
onClickButton={handleClose}
37+
onClickButton={headerCommands.handleClose}
4938
buttonText={t('exit')}
5039
>
5140
<ModalContainer
@@ -82,7 +71,7 @@ export function LPCErrorModal(props: LPCWizardContentProps): JSX.Element {
8271
<PrimaryButton
8372
textTransform={TEXT_TRANSFORM_CAPITALIZE}
8473
alignSelf={ALIGN_FLEX_END}
85-
onClick={handleClose}
74+
onClick={headerCommands.handleClose}
8675
>
8776
{t('shared:exit')}
8877
</PrimaryButton>

app/src/organisms/LabwarePositionCheck/LPCProbeNotAttached.tsx

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,27 @@ import { useTranslation } from 'react-i18next'
44
import { ProbeNotAttached } from '/app/organisms/PipetteWizardFlows/ProbeNotAttached'
55
import { getIsOnDevice } from '/app/redux/config'
66
import { LPCContentContainer } from '/app/organisms/LabwarePositionCheck/LPCContentContainer'
7-
import { LPC_STEP, selectActivePipette } from '/app/redux/protocol-runs'
87

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

1110
// TODO(jh, 02-05-25): EXEC-1190.
1211
export function LPCProbeNotAttached(props: LPCWizardContentProps): JSX.Element {
1312
const { t } = useTranslation('labware_position_check')
14-
const { commandUtils, proceedStep, runId } = props
15-
const {
16-
setShowUnableToDetect,
17-
toggleRobotMoving,
18-
handleValidMoveToMaintenancePosition,
19-
handleProbeAttachment,
20-
} = commandUtils
13+
const { commandUtils } = props
14+
const { setShowUnableToDetect, headerCommands } = commandUtils
2115
const isOnDevice = useSelector(getIsOnDevice)
22-
const pipette = useSelector(selectActivePipette(runId))
23-
24-
const handleAttachProbeCheck = (): void => {
25-
void toggleRobotMoving(true)
26-
.then(() => handleProbeAttachment(pipette, proceedStep))
27-
.then(() => {
28-
proceedStep()
29-
})
30-
.finally(() => toggleRobotMoving(false))
31-
}
32-
33-
const handleNavToDetachProbe = (): void => {
34-
void toggleRobotMoving(true)
35-
.then(() => handleValidMoveToMaintenancePosition(pipette))
36-
.then(() => {
37-
proceedStep(LPC_STEP.DETACH_PROBE)
38-
})
39-
.finally(() => toggleRobotMoving(false))
40-
}
4116

4217
return (
4318
<LPCContentContainer
4419
{...props}
4520
header={t('labware_position_check_title')}
4621
buttonText={t('try_again')}
47-
onClickButton={handleAttachProbeCheck}
22+
onClickButton={headerCommands.handleAttachProbeCheck}
4823
secondaryButtonProps={{
4924
buttonText: t('exit'),
5025
buttonCategory: 'rounded',
5126
buttonType: 'tertiaryLowLight',
52-
onClick: handleNavToDetachProbe,
27+
onClick: headerCommands.handleNavToDetachProbe,
5328
}}
5429
>
5530
<ProbeNotAttached

app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { getIsOnDevice } from '/app/redux/config'
3232
import type { LPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows'
3333
import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'
3434
import type { LPCStep } from '/app/redux/protocol-runs'
35+
import { useLPCHeaderCommands } from '/app/organisms/LabwarePositionCheck/hooks/useLPCCommands/useLPCHeaderCommands'
3536

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

@@ -60,12 +61,19 @@ export function LPCWizardFlex(props: LPCWizardFlexProps): JSX.Element {
6061
}
6162
}, [])
6263

64+
const headerCommands = useLPCHeaderCommands({
65+
...props,
66+
LPCHandlerUtils,
67+
proceedStep,
68+
goBackLastStep,
69+
})
70+
6371
return (
6472
<LPCWizardFlexComponent
6573
{...props}
6674
proceedStep={proceedStep}
6775
goBackLastStep={goBackLastStep}
68-
commandUtils={LPCHandlerUtils}
76+
commandUtils={{ ...LPCHandlerUtils, headerCommands }}
6977
/>
7078
)
7179
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { describe, beforeEach, afterEach, it, vi, expect } from 'vitest'
2+
import { renderHook, act, waitFor } from '@testing-library/react'
3+
import { useSelector, Provider } from 'react-redux'
4+
import { createStore } from 'redux'
5+
import { I18nextProvider } from 'react-i18next'
6+
7+
import { i18n } from '/app/i18n'
8+
import { LPC_STEP } from '/app/redux/protocol-runs'
9+
import { useLPCHeaderCommands } from '../useLPCHeaderCommands'
10+
11+
import type { FunctionComponent, ReactNode } from 'react'
12+
import type { UseLPCCommandsResult } from '/app/organisms/LabwarePositionCheck/hooks'
13+
import type { UseLPCHeaderCommandsProps } from '../useLPCHeaderCommands'
14+
import type { State } from '/app/redux/types'
15+
import type { Store } from 'redux'
16+
17+
vi.mock('react-redux', async importOriginal => {
18+
const actual = await importOriginal<typeof Provider>()
19+
return {
20+
...actual,
21+
useSelector: vi.fn(),
22+
}
23+
})
24+
25+
let props: UseLPCHeaderCommandsProps
26+
let mockLPCHandlerUtils: UseLPCCommandsResult
27+
let wrapper: FunctionComponent<{ children: ReactNode }>
28+
let store: Store<State>
29+
let toggleRobotMovingPromise: Promise<void>
30+
let handleStartLPCPromise: Promise<void>
31+
let handleProbeAttachmentPromise: Promise<void>
32+
let handleValidMoveToMaintenancePositionPromise: Promise<void>
33+
let handleCleanUpAndClosePromise: Promise<void>
34+
35+
describe('useLPCHeaderCommands', () => {
36+
const mockPipette = { id: 'mock-pipette' }
37+
const mockRunId = 'mock-run-id'
38+
const mockProceedStep = vi.fn()
39+
40+
beforeEach(() => {
41+
toggleRobotMovingPromise = Promise.resolve()
42+
handleStartLPCPromise = Promise.resolve()
43+
handleProbeAttachmentPromise = Promise.resolve()
44+
handleValidMoveToMaintenancePositionPromise = Promise.resolve()
45+
handleCleanUpAndClosePromise = Promise.resolve()
46+
47+
mockLPCHandlerUtils = {
48+
toggleRobotMoving: vi.fn(() => toggleRobotMovingPromise),
49+
handleStartLPC: vi.fn(() => handleStartLPCPromise),
50+
handleProbeAttachment: vi.fn(() => handleProbeAttachmentPromise),
51+
handleValidMoveToMaintenancePosition: vi.fn(
52+
() => handleValidMoveToMaintenancePositionPromise
53+
),
54+
handleCleanUpAndClose: vi.fn(() => handleCleanUpAndClosePromise),
55+
} as any
56+
57+
props = {
58+
LPCHandlerUtils: mockLPCHandlerUtils,
59+
proceedStep: mockProceedStep,
60+
goBackLastStep: vi.fn(),
61+
runId: mockRunId,
62+
}
63+
64+
store = createStore(vi.fn(), {})
65+
store.dispatch = vi.fn()
66+
67+
wrapper = ({ children }) => (
68+
<I18nextProvider i18n={i18n}>
69+
<Provider store={store}>{children}</Provider>
70+
</I18nextProvider>
71+
)
72+
73+
vi.mocked(useSelector).mockImplementation(() => {
74+
return mockPipette
75+
})
76+
})
77+
78+
afterEach(() => {
79+
vi.clearAllMocks()
80+
})
81+
82+
it('should execute handleProceed commands in correct sequence', async () => {
83+
const { result } = renderHook(() => useLPCHeaderCommands(props), {
84+
wrapper,
85+
})
86+
87+
await act(async () => {
88+
result.current.handleProceed()
89+
})
90+
91+
await waitFor(() => {
92+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
93+
})
94+
95+
await waitFor(() => {
96+
expect(mockLPCHandlerUtils.handleStartLPC).toHaveBeenCalledWith(
97+
mockPipette,
98+
mockProceedStep
99+
)
100+
})
101+
102+
await waitFor(() => {
103+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
104+
})
105+
})
106+
107+
it('should execute handleAttachProbeCheck commands in correct sequence', async () => {
108+
const { result } = renderHook(() => useLPCHeaderCommands(props), {
109+
wrapper,
110+
})
111+
112+
await act(async () => {
113+
result.current.handleAttachProbeCheck()
114+
})
115+
116+
await waitFor(() => {
117+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
118+
})
119+
120+
await waitFor(() => {
121+
expect(mockLPCHandlerUtils.handleProbeAttachment).toHaveBeenCalledWith(
122+
mockPipette,
123+
mockProceedStep
124+
)
125+
})
126+
127+
await waitFor(() => {
128+
expect(mockProceedStep).toHaveBeenCalled()
129+
})
130+
131+
await waitFor(() => {
132+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
133+
})
134+
})
135+
136+
it('should execute handleNavToDetachProbe commands in correct sequence', async () => {
137+
const { result } = renderHook(() => useLPCHeaderCommands(props), {
138+
wrapper,
139+
})
140+
141+
await act(async () => {
142+
result.current.handleNavToDetachProbe()
143+
})
144+
145+
await waitFor(() => {
146+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
147+
})
148+
149+
await waitFor(() => {
150+
expect(
151+
mockLPCHandlerUtils.handleValidMoveToMaintenancePosition
152+
).toHaveBeenCalledWith(mockPipette)
153+
})
154+
155+
await waitFor(() => {
156+
expect(mockProceedStep).toHaveBeenCalledWith(LPC_STEP.DETACH_PROBE)
157+
})
158+
159+
await waitFor(() => {
160+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(false)
161+
})
162+
})
163+
164+
it('should execute handleClose commands in correct sequence', async () => {
165+
const { result } = renderHook(() => useLPCHeaderCommands(props), {
166+
wrapper,
167+
})
168+
169+
await act(async () => {
170+
result.current.handleClose()
171+
})
172+
173+
await waitFor(() => {
174+
expect(mockLPCHandlerUtils.toggleRobotMoving).toHaveBeenCalledWith(true)
175+
})
176+
177+
await waitFor(() => {
178+
expect(mockLPCHandlerUtils.handleCleanUpAndClose).toHaveBeenCalled()
179+
})
180+
})
181+
})
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useSelector } from 'react-redux'
2+
3+
import { LPC_STEP, selectActivePipette } from '/app/redux/protocol-runs'
4+
5+
import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'
6+
import type { UseLPCCommandsResult } from '/app/organisms/LabwarePositionCheck/hooks'
7+
8+
export type UseLPCHeaderCommandsProps = Omit<
9+
LPCWizardContentProps,
10+
'commandUtils'
11+
> & {
12+
LPCHandlerUtils: UseLPCCommandsResult
13+
}
14+
15+
export interface UseLPCHeaderCommandsResult {
16+
handleProceed: () => void
17+
handleAttachProbeCheck: () => void
18+
handleNavToDetachProbe: () => void
19+
handleClose: () => void
20+
}
21+
22+
// Wraps core LPC command functionality, since the header component reuses many of the same commands.
23+
export function useLPCHeaderCommands({
24+
LPCHandlerUtils,
25+
proceedStep,
26+
runId,
27+
}: UseLPCHeaderCommandsProps): UseLPCHeaderCommandsResult {
28+
const activePipette = useSelector(selectActivePipette(runId))
29+
const pipette = useSelector(selectActivePipette(runId))
30+
31+
const {
32+
handleStartLPC,
33+
toggleRobotMoving,
34+
handleValidMoveToMaintenancePosition,
35+
handleCleanUpAndClose,
36+
handleProbeAttachment,
37+
} = LPCHandlerUtils
38+
39+
const handleProceed = (): void => {
40+
void toggleRobotMoving(true)
41+
.then(() => handleStartLPC(activePipette, proceedStep))
42+
.finally(() => toggleRobotMoving(false))
43+
}
44+
45+
// If the maintenance run fails, we cannot move the gantry, so just clean up LPC.
46+
const handleClose = (): void => {
47+
void toggleRobotMoving(true).then(() => {
48+
void handleCleanUpAndClose()
49+
})
50+
}
51+
52+
const handleAttachProbeCheck = (): void => {
53+
void toggleRobotMoving(true)
54+
.then(() => handleProbeAttachment(pipette, proceedStep))
55+
.then(() => {
56+
proceedStep()
57+
})
58+
.finally(() => toggleRobotMoving(false))
59+
}
60+
61+
const handleNavToDetachProbe = (): void => {
62+
void toggleRobotMoving(true)
63+
.then(() => handleValidMoveToMaintenancePosition(pipette))
64+
.then(() => {
65+
proceedStep(LPC_STEP.DETACH_PROBE)
66+
})
67+
.finally(() => toggleRobotMoving(false))
68+
}
69+
70+
return {
71+
handleProceed,
72+
handleAttachProbeCheck,
73+
handleNavToDetachProbe,
74+
handleClose,
75+
}
76+
}

0 commit comments

Comments
 (0)