Skip to content

Commit

Permalink
feat(app): heater shaker wizard test shake (#9549)
Browse files Browse the repository at this point in the history
This PR implements the test shake page of the heater shaker wizard.
  • Loading branch information
sakibh authored Mar 2, 2022
1 parent 7d67da4 commit fa6f9c6
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 12 deletions.
9 changes: 9 additions & 0 deletions app/src/assets/images/heatershaker_module_transparent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 8 additions & 3 deletions app/src/assets/localization/en/device_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"disengage_slideout": "Disengaged",
"engage_height_slideout": "Engage height",
"gen2_num_slideout": "{{num}} mm",
"set_engage_height_slideout_btn": "Set Engage Height",
"set_engage_height": "Set Engage Height",
"tc_lid": "Lid",
"tc_block": "Block",
"overflow_menu_engage": "Set engage height",
Expand All @@ -36,7 +36,7 @@
"tempdeck_slideout_title": "Set Temperature for {{name}}",
"tempdeck_slideout_body": "Pre heat or cool your {{model}}. Enter a whole number between 4 °C and 96 °C.",
"temperature": "Temperature",
"set_temp_slideout_btn": "Set Temperature",
"set_temp_slideout": "Set Temperature",
"tc_set_temperature": "Set {{part}} Temperature for {{name}}",
"tc_set_temperature_body": "Pre heat or cool your Thermocycler {{part}}. Enter a whole number between {{min}} °C and {{max}} °C.",
"set_tc_temp_slideout": "Set {{part}} Temperature",
Expand All @@ -46,5 +46,10 @@
"serial_number": "Serial Number",
"link_firmware_update": "View Firmware Update",
"firmware_update_available": "Firmware update available.",
"view_update": "View Update"
"view_update": "View Update",
"heater": "Heater",
"shaker": "Shaker",
"target_speed": "Target: {{speed}} RPM",
"current_speed": "Current: {{speed}} RPM",
"na_speed": "Target: N/A RPM"
}
13 changes: 12 additions & 1 deletion app/src/assets/localization/en/heater_shaker.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,16 @@
"heater_shaker_key_parts": "Key Heater Shaker parts and terminology",
"heater_shaker_orient_module": "Orient the module so its power ports face <bold>away</bold> from you.",
"heater_shaker_latch_description": "<block>The <strong>Labware Latch</strong> keeps labware secure while the module is shaking.</block> <block>It can be opened or closed manually and with software but is closed and locked while the module is shaking.</block>",
"heater_shaker_anchor_description": "<block>The 2 <strong>Anchors</strong> keep the module attached to the deck while it is shaking.</block> <block>The screw above each anchor is used to extend and retract them. See animation below.</block> <block>Extending the bolts slightly increases the module’s footprint, which allows it to be more firmly attached to the edges of a slot.</block>"
"heater_shaker_anchor_description": "<block>The 2 <strong>Anchors</strong> keep the module attached to the deck while it is shaking.</block> <block>The screw above each anchor is used to extend and retract them. See animation below.</block> <block>Extending the bolts slightly increases the module’s footprint, which allows it to be more firmly attached to the edges of a slot.</block>",
"step_4_of_4": "Step 4 of 4: Test shake",
"test_shake_banner_information": "If you want to add labware to the module before doing a test shake, you can use the labware latch controls to hold the latches open.",
"open_labware_latch": "Open Labware Latch",
"set_shake_speed": "Set shake speed",
"start_shaking": "Start Shaking",
"troubleshooting": "Troubleshooting",
"troubleshoot_step1_description": "Return to Step 1 to see instructions for securing the module to the deck.",
"troubleshoot_step2_description": "Return to Step 2 to see instructions for securing the thermal adapter to the module.",
"go_to_step_1": "Go to Step 1",
"go_to_step_2": "Go to Step 2",
"min_max_rpm": "{{min}} - {{max}} RPM"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as React from 'react'
import {
Flex,
Text,
Icon,
SIZE_1,
TYPOGRAPHY,
TEXT_TRANSFORM_UPPERCASE,
DIRECTION_ROW,
DIRECTION_COLUMN,
SPACING,
ALIGN_FLEX_START,
COLORS,
} from '@opentrons/components'
import heaterShakerModule from '../../../assets/images/heatershaker_module_transparent.svg'
import { HeaterShakerModuleData } from '../ModuleCard/HeaterShakerModuleData'

export const HeaterShakerModuleCard = (): JSX.Element | null => {
return (
<Flex
backgroundColor={COLORS.background}
borderRadius={SPACING.spacing2}
marginBottom={SPACING.spacing3}
marginLeft={SPACING.spacing3}
padding={`${SPACING.spacing4} ${SPACING.spacing3} ${SPACING.spacing4} ${SPACING.spacing3}`}
width={'20rem'}
>
<Flex
flexDirection={DIRECTION_ROW}
paddingRight={SPACING.spacing3}
alignItems={ALIGN_FLEX_START}
>
<img src={heaterShakerModule} alt={'Heater Shaker'} />
<Flex flexDirection={DIRECTION_COLUMN} paddingLeft={SPACING.spacing3}>
<Text
textTransform={TEXT_TRANSFORM_UPPERCASE}
color={COLORS.darkGreyEnabled}
fontWeight={TYPOGRAPHY.fontWeightRegular}
fontSize={TYPOGRAPHY.fontSizeCaption}
paddingBottom={SPACING.spacing2}
>
{'USB Port'}
</Text>
<Flex paddingBottom={SPACING.spacing2}>
<Icon
name={'heater-shaker'}
size={SIZE_1}
marginRight={SPACING.spacing2}
color={COLORS.darkGreyEnabled}
/>
<Text fontSize={TYPOGRAPHY.fontSizeP}>{'Heater/Shaker GENX'}</Text>
</Flex>
<HeaterShakerModuleData
// TODO(sh, 2022-02-22): replace stubbed out props with actual module values
heaterStatus={'idle'}
shakerStatus={'shaking'}
latchStatus={'Closed and locked'}
targetTemp={0}
currentTemp={0}
targetSpeed={0}
currentSpeed={0}
showTemperatureData={false}
/>
</Flex>
</Flex>
</Flex>
)
}
136 changes: 134 additions & 2 deletions app/src/organisms/Devices/HeaterShakerWizard/TestShake.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,137 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
ALIGN_FLEX_START,
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
Flex,
Icon,
InputField,
SIZE_AUTO,
SPACING,
Text,
TYPOGRAPHY,
} from '@opentrons/components'
import { RPM } from '@opentrons/shared-data'
import { HeaterShakerModuleCard } from './HeaterShakerModuleCard'
import { TertiaryButton } from '../../../atoms/Buttons'
import { CollapsibleStep } from '../../ProtocolSetup/RunSetupCard/CollapsibleStep'
import { Divider } from '../../../atoms/structure'

export function TestShake(): JSX.Element {
return <div>TestShake</div>
interface TestShakeProps {
setCurrentPage: React.Dispatch<React.SetStateAction<number>>
}

export function TestShake(props: TestShakeProps): JSX.Element {
const { setCurrentPage } = props
const { t } = useTranslation('heater_shaker')

const [isExpanded, setExpanded] = React.useState(false)

return (
<Flex flexDirection={DIRECTION_COLUMN}>
<Text
color={COLORS.darkBlack}
fontSize={TYPOGRAPHY.fontSizeH2}
fontWeight={700}
>
{t('step_4_of_4')}
</Text>
<Flex
marginTop={SPACING.spacing3}
marginBottom={SPACING.spacing4}
backgroundColor={COLORS.background}
paddingTop={SPACING.spacing4}
paddingLeft={SPACING.spacing4}
flexDirection={DIRECTION_ROW}
data-testid={'test_shake_banner_info'}
>
<Flex
size={SPACING.spacing6}
color={COLORS.darkGreyEnabled}
paddingBottom={SPACING.spacing4}
>
<Icon name="information" aria-label="information" />
</Flex>
<Flex
flexDirection={DIRECTION_COLUMN}
paddingLeft={SPACING.spacing3}
fontSize={TYPOGRAPHY.fontSizeP}
paddingBottom={SPACING.spacing4}
>
<Text fontWeight={TYPOGRAPHY.fontWeightRegular}>
{/* TODO(sh, 2022-02-22): Dynamically render this text if a labware/protocol exists */}
{t('test_shake_banner_information')}
</Text>
</Flex>
</Flex>
<Flex
alignSelf={ALIGN_CENTER}
flexDirection={DIRECTION_COLUMN}
fontSize={TYPOGRAPHY.fontSizeCaption}
>
<HeaterShakerModuleCard />
<TertiaryButton marginLeft={SIZE_AUTO} marginTop={SPACING.spacing4}>
{t('open_labware_latch')}
</TertiaryButton>
<Flex
flexDirection={DIRECTION_ROW}
marginY={SPACING.spacingL}
alignItems={ALIGN_FLEX_START}
>
<Flex flexDirection={DIRECTION_COLUMN} maxWidth={'6.25rem'}>
<Text fontSize={TYPOGRAPHY.fontSizeCaption}>
{t('set_shake_speed')}
</Text>
{/* TODO(sh, 2022-02-22): Wire up input when end points are updated */}
<InputField units={RPM} value={'1000'} readOnly />
<Text fontSize={TYPOGRAPHY.fontSizeCaption}>
{t('min_max_rpm', { min: '200', max: '1800' })}
</Text>
</Flex>
<TertiaryButton
fontSize={TYPOGRAPHY.fontSizeCaption}
marginLeft={SIZE_AUTO}
marginTop={SPACING.spacing3}
>
{t('start_shaking')}
</TertiaryButton>
</Flex>
</Flex>
<Divider marginY={SPACING.spacing4} />
<CollapsibleStep
expanded={isExpanded}
title={t('troubleshooting')}
toggleExpanded={() => setExpanded(!isExpanded)}
>
<Flex
flexDirection={DIRECTION_ROW}
alignItems={ALIGN_FLEX_START}
marginY={SPACING.spacing6}
>
<Text width={'22rem'}>{t('troubleshoot_step1_description')}</Text>
<TertiaryButton
fontSize={TYPOGRAPHY.fontSizeCaption}
marginLeft={SIZE_AUTO}
onClick={() => setCurrentPage(2)}
>
{t('go_to_step_1')}
</TertiaryButton>
</Flex>
<Flex flexDirection={DIRECTION_ROW} alignItems={ALIGN_FLEX_START}>
<Text width={'22rem'}>{t('troubleshoot_step2_description')}</Text>
<TertiaryButton
fontSize={TYPOGRAPHY.fontSizeCaption}
marginLeft={SIZE_AUTO}
onClick={() => setCurrentPage(3)}
>
{t('go_to_step_2')}
</TertiaryButton>
</Flex>
</CollapsibleStep>
<Divider marginTop={SPACING.spacing4} marginBottom={SPACING.spacingXL} />
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../../i18n'
import { TestShake } from '../TestShake'
import { HeaterShakerModuleCard } from '../HeaterShakerModuleCard'
import { fireEvent } from '@testing-library/react'

jest.mock('../HeaterShakerModuleCard')

const mockHeaterShakerModuleCard = HeaterShakerModuleCard as jest.MockedFunction<
typeof HeaterShakerModuleCard
>

const render = (props: React.ComponentProps<typeof TestShake>) => {
return renderWithProviders(<TestShake {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('TestShake', () => {
let props: React.ComponentProps<typeof TestShake>
beforeEach(() => {
props = {
setCurrentPage: jest.fn(),
}
mockHeaterShakerModuleCard.mockReturnValue(
<div>Mock Heater Shaker Module Card</div>
)
})
it('renders the correct title', () => {
const { getByText } = render(props)
getByText('Step 4 of 4: Test shake')
})

it('renders the information banner icon and description', () => {
const { getByText, getByLabelText } = render(props)
getByLabelText('information')
getByText(
'If you want to add labware to the module before doing a test shake, you can use the labware latch controls to hold the latches open.'
)
})

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', () => {
const { getByRole } = render(props)
const button = getByRole('button', { name: /Open Labware Latch/i })
expect(button).toBeEnabled()
})

it('renders the start shaking button and is enabled', () => {
const { getByRole } = render(props)
const button = getByRole('button', { name: /Start Shaking/i })
expect(button).toBeEnabled()
})

it('renders an input field for speed setting', () => {
const { getByText, getByRole } = render(props)

getByText('Set shake speed')
getByRole('textbox')
})

it('renders troubleshooting accordion and contents', () => {
const { getByText, getByRole } = render(props)

const troubleshooting = getByText('Troubleshooting')
fireEvent.click(troubleshooting)

getByText(
'Return to Step 1 to see instructions for securing the module to the deck.'
)
const buttonStep1 = getByRole('button', { name: /Go to Step 1/i })
expect(buttonStep1).toBeEnabled()

getByText(
'Return to Step 2 to see instructions for securing the thermal adapter to the module.'
)
const buttonStep2 = getByRole('button', { name: /Go to Step 2/i })
expect(buttonStep2).toBeEnabled()
})
})
2 changes: 1 addition & 1 deletion app/src/organisms/Devices/HeaterShakerWizard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const HeaterShakerWizard = (
return <PowerOn attachedModule={heaterShaker ?? null} />
case 5:
buttonContent = t('complete')
return <TestShake />
return <TestShake setCurrentPage={setCurrentPage} />
default:
return null
}
Expand Down
Loading

0 comments on commit fa6f9c6

Please sign in to comment.