+ let mockCreateLiveCommand = jest.fn()
beforeEach(() => {
props = {
setCurrentPage: jest.fn(),
module: mockHeaterShaker,
+ hasProtocol: false,
}
+ mockCreateLiveCommand = jest.fn()
+ mockCreateLiveCommand.mockResolvedValue(null)
+ mockUseLiveCommandMutation.mockReturnValue({
+ createLiveCommand: mockCreateLiveCommand,
+ } as any)
mockHeaterShakerModuleCard.mockReturnValue(
Mock Heater Shaker Module Card
)
+ mockUseLatchCommand.mockReturnValue({
+ handleLatch: jest.fn(),
+ isLatchClosed: true,
+ } as any)
+ mockUseHeaterShakerFromProtocol.mockReturnValue(
+ HEATER_SHAKER_PROTOCOL_MODULE_INFO
+ )
})
it('renders the correct title', () => {
const { getByText } = render(props)
@@ -42,19 +157,50 @@ describe('TestShake', () => {
)
})
+ it('renders labware name in the banner description when there is a protocol', () => {
+ props = {
+ setCurrentPage: jest.fn(),
+ module: mockHeaterShaker,
+ hasProtocol: true,
+ }
+ const { getByText } = render(props)
+ getByText(
+ nestedTextMatcher(
+ 'If you want to add the Source Plate to the module before doing a test shake, you can use the labware latch controls.'
+ )
+ )
+ })
+
it('renders a heater shaker module card', () => {
const { getByText } = render(props)
getByText('Mock Heater Shaker Module Card')
})
- it('renders the open labware latch button and is enabled', () => {
+ it('renders the close labware latch button and is enabled when latch status is open', () => {
+ props = {
+ module: mockHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ mockUseLatchCommand.mockReturnValue({
+ toggleLatch: jest.fn(),
+ isLatchClosed: false,
+ })
+
const { getByRole } = render(props)
- const button = getByRole('button', { name: /Open Labware Latch/i })
+ const button = getByRole('button', { name: /Close Labware Latch/i })
expect(button).toBeEnabled()
})
it('renders the start shaking button and is enabled', () => {
+ props = {
+ module: mockCloseLatchHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
const { getByRole } = render(props)
const button = getByRole('button', { name: /Start Shaking/i })
expect(button).toBeEnabled()
@@ -64,7 +210,7 @@ describe('TestShake', () => {
const { getByText, getByRole } = render(props)
getByText('Set shake speed')
- getByRole('textbox')
+ getByRole('spinbutton')
})
it('renders troubleshooting accordion and contents', () => {
@@ -85,4 +231,104 @@ describe('TestShake', () => {
const buttonStep2 = getByRole('button', { name: /Go to Step 2/i })
expect(buttonStep2).toBeEnabled()
})
+
+ it('start shake button should be disabled if the labware latch is open', () => {
+ props = {
+ module: mockOpenLatchHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ mockUseLatchCommand.mockReturnValue({
+ toggleLatch: jest.fn(),
+ isLatchClosed: false,
+ })
+
+ const { getByRole } = render(props)
+ const button = getByRole('button', { name: /Start/i })
+ expect(button).toBeDisabled()
+ })
+
+ it('clicking the open latch button should open the heater shaker latch', () => {
+ props = {
+ module: mockCloseLatchHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ mockUseLatchCommand.mockReturnValue({
+ toggleLatch: jest.fn(),
+ isLatchClosed: true,
+ })
+
+ const { getByRole } = render(props)
+ const button = getByRole('button', { name: /Open Labware Latch/i })
+ fireEvent.click(button)
+ expect(mockUseLatchCommand).toHaveBeenCalled()
+ })
+
+ it('clicking the close latch button should close the heater shaker latch', () => {
+ props = {
+ module: mockOpenLatchHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ mockUseLatchCommand.mockReturnValue({
+ toggleLatch: jest.fn(),
+ isLatchClosed: false,
+ })
+
+ const { getByRole } = render(props)
+ const button = getByRole('button', { name: /Close Labware Latch/i })
+ fireEvent.click(button)
+ expect(mockUseLatchCommand).toHaveBeenCalled()
+ })
+
+ it('entering an input for shake speed and clicking start should begin shaking', () => {
+ props = {
+ module: mockCloseLatchHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ const { getByRole } = render(props)
+ const button = getByRole('button', { name: /Start Shaking/i })
+ const input = getByRole('spinbutton')
+ fireEvent.change(input, { target: { value: '300' } })
+ fireEvent.click(button)
+
+ expect(mockCreateLiveCommand).toHaveBeenCalledWith({
+ command: {
+ commandType: 'heaterShakerModule/setTargetShakeSpeed',
+ params: {
+ moduleId: 'heatershaker_id',
+ rpm: 300,
+ },
+ },
+ })
+ })
+
+ it('when the heater shaker is shaking clicking stop should deactivate the shaking', () => {
+ props = {
+ module: mockMovingHeaterShaker,
+ setCurrentPage: jest.fn(),
+ hasProtocol: false,
+ }
+
+ const { getByRole } = render(props)
+ const button = getByRole('button', { name: /Stop Shaking/i })
+ const input = getByRole('spinbutton')
+ fireEvent.change(input, { target: { value: '0' } })
+ fireEvent.click(button)
+
+ expect(mockCreateLiveCommand).toHaveBeenCalledWith({
+ command: {
+ commandType: 'heaterShakerModule/stopShake',
+ params: {
+ moduleId: mockHeaterShaker.id,
+ },
+ },
+ })
+ })
})
diff --git a/app/src/organisms/Devices/HeaterShakerWizard/index.tsx b/app/src/organisms/Devices/HeaterShakerWizard/index.tsx
index 7240d449ac2..b1076613b05 100644
--- a/app/src/organisms/Devices/HeaterShakerWizard/index.tsx
+++ b/app/src/organisms/Devices/HeaterShakerWizard/index.tsx
@@ -1,13 +1,9 @@
import * as React from 'react'
+import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { Portal } from '../../../App/portal'
-import { useSelector } from 'react-redux'
-import { getConnectedRobotName } from '../../../redux/robot/selectors'
import { Interstitial } from '../../../atoms/Interstitial/Interstitial'
-import {
- getAttachedModules,
- HEATERSHAKER_MODULE_TYPE,
-} from '../../../redux/modules'
+import { HEATERSHAKER_MODULE_TYPE } from '../../../redux/modules'
import { PrimaryButton, SecondaryButton } from '../../../atoms/Buttons'
import { Introduction } from './Introduction'
import { KeyParts } from './KeyParts'
@@ -24,23 +20,23 @@ import {
useHoverTooltip,
} from '@opentrons/components'
-import type { State } from '../../../redux/types'
+import type { NextGenRouteParams } from '../../../App/NextGenApp'
import type { HeaterShakerModule } from '../../../redux/modules/types'
+import { useAttachedModules } from '../hooks'
interface HeaterShakerWizardProps {
onCloseClick: () => unknown
+ hasProtocol?: boolean
}
export const HeaterShakerWizard = (
props: HeaterShakerWizardProps
): JSX.Element | null => {
- const { onCloseClick } = props
+ const { onCloseClick, hasProtocol } = props
const { t } = useTranslation(['heater_shaker', 'shared'])
const [currentPage, setCurrentPage] = React.useState(0)
- const robotName = useSelector((state: State) => getConnectedRobotName(state))
- const attachedModules = useSelector((state: State) =>
- getAttachedModules(state, robotName === null ? null : robotName)
- )
+ const { robotName } = useParams()
+ const attachedModules = useAttachedModules(robotName)
const [targetProps, tooltipProps] = useHoverTooltip()
const heaterShaker = (attachedModules.find(
@@ -78,7 +74,11 @@ export const HeaterShakerWizard = (
case 5:
buttonContent = t('complete')
return (
-
+
)
default:
return null
diff --git a/app/src/organisms/Devices/ModuleCard/HeaterShakerModuleData.tsx b/app/src/organisms/Devices/ModuleCard/HeaterShakerModuleData.tsx
index dd4984b3a95..6226f0631d8 100644
--- a/app/src/organisms/Devices/ModuleCard/HeaterShakerModuleData.tsx
+++ b/app/src/organisms/Devices/ModuleCard/HeaterShakerModuleData.tsx
@@ -13,13 +13,21 @@ import {
DIRECTION_ROW,
TYPOGRAPHY,
C_SKY_BLUE,
+ TEXT_TRANSFORM_CAPITALIZE,
+ SIZE_1,
} from '@opentrons/components'
import { StatusLabel } from '../../../atoms/StatusLabel'
+import type {
+ LatchStatus,
+ SpeedStatus,
+ TemperatureStatus,
+} from '../../../redux/modules/api-types'
+
interface HeaterShakerModuleDataProps {
- heaterStatus: string
- shakerStatus: string
- latchStatus: string
+ heaterStatus: TemperatureStatus
+ shakerStatus: SpeedStatus
+ latchStatus: LatchStatus
targetTemp: number | null
currentTemp: number | null
targetSpeed: number | null
@@ -40,7 +48,8 @@ export const HeaterShakerModuleData = (
currentSpeed,
showTemperatureData,
} = props
- const { t } = useTranslation(['device_details', 'heater_shaker'])
+ const { t } = useTranslation(['device_details', 'heater_shaker', 'shared'])
+ const isShaking = shakerStatus !== 'idle'
const getStatusLabelProps = (
status: string | null
@@ -75,6 +84,38 @@ export const HeaterShakerModuleData = (
return StatusLabelProps
}
+ const getLatchStatus = (latchStatus: LatchStatus): JSX.Element | string => {
+ switch (latchStatus) {
+ case 'opening':
+ case 'idle_open':
+ case 'idle_unknown': {
+ return (
+
+ {t('open', { ns: 'shared' })}
+
+ )
+ }
+ case 'closing':
+ case 'idle_closed': {
+ if (isShaking) {
+ return (
+
+ {t('closed_and_locked', { ns: 'heater_shaker' })}
+
+ )
+ } else {
+ return (
+
+ {t('closed', { ns: 'heater_shaker' })}
+
+ )
+ }
+ }
+ default:
+ return latchStatus
+ }
+ }
+
return (
<>
@@ -162,13 +203,14 @@ export const HeaterShakerModuleData = (
alignItems={ALIGN_FLEX_START}
>
- {/* {TODO(sh, 2022-02-22): Conditionally render icon based on latch status} */}
-
- {latchStatus}
+ {isShaking && (
+
+ )}
+ {getLatchStatus(latchStatus)}
diff --git a/app/src/organisms/Devices/ModuleCard/TestShakeSlideout.tsx b/app/src/organisms/Devices/ModuleCard/TestShakeSlideout.tsx
index b3a82d560ff..13ff2204364 100644
--- a/app/src/organisms/Devices/ModuleCard/TestShakeSlideout.tsx
+++ b/app/src/organisms/Devices/ModuleCard/TestShakeSlideout.tsx
@@ -92,7 +92,7 @@ export const TestShakeSlideout = (
const errorMessage =
shakeValue != null &&
(parseInt(shakeValue) < HS_RPM_MIN || parseInt(shakeValue) > HS_RPM_MAX)
- ? t('input_out_of_range')
+ ? t('input_out_of_range', { ns: 'device_details' })
: null
return (
diff --git a/app/src/organisms/Devices/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx b/app/src/organisms/Devices/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx
index 998f8bb2427..a1dc8cfacf9 100644
--- a/app/src/organisms/Devices/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx
+++ b/app/src/organisms/Devices/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx
@@ -70,7 +70,7 @@ describe('ConfirmAttachmentBanner', () => {
expect(props.onCloseClick).toHaveBeenCalled()
})
- it('renders the correct modal info when accessed through proceed to run CTA and clicks proceed to run button ', () => {
+ it('renders the correct modal info when accessed through proceed to run CTA and clicks proceed to run button', () => {
props = {
onCloseClick: jest.fn(),
isProceedToRunModal: true,
diff --git a/app/src/organisms/Devices/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx b/app/src/organisms/Devices/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx
index 289c11b8454..46fb26c464a 100644
--- a/app/src/organisms/Devices/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx
+++ b/app/src/organisms/Devices/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx
@@ -73,8 +73,8 @@ describe('HeaterShakerModuleData', () => {
it('renders a shaking status', () => {
props = {
- heaterStatus: 'shaking',
- shakerStatus: 'idle',
+ heaterStatus: 'idle',
+ shakerStatus: 'speeding up',
latchStatus: 'idle_unknown',
targetTemp: null,
currentTemp: null,
@@ -93,6 +93,6 @@ describe('HeaterShakerModuleData', () => {
const { getByText } = render(props)
getByText('Target: N/A RPM')
getByText('Labware Latch')
- getByText('idle_unknown')
+ getByText(/Open/i)
})
})
diff --git a/app/src/organisms/ProtocolSetup/RunSetupCard/ModuleSetup/HeaterShakerSetupWizard/HeaterShakerBanner.tsx b/app/src/organisms/ProtocolSetup/RunSetupCard/ModuleSetup/HeaterShakerSetupWizard/HeaterShakerBanner.tsx
index c007965c4fc..3cc04454c6c 100644
--- a/app/src/organisms/ProtocolSetup/RunSetupCard/ModuleSetup/HeaterShakerSetupWizard/HeaterShakerBanner.tsx
+++ b/app/src/organisms/ProtocolSetup/RunSetupCard/ModuleSetup/HeaterShakerSetupWizard/HeaterShakerBanner.tsx
@@ -17,7 +17,10 @@ export function HeaterShakerBanner(
return (
<>
{showWizard && (
- setShowWizard(false)} />
+ setShowWizard(false)}
+ hasProtocol={true}
+ />
)}