Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): extend SectionList component for Generic Step Screen #8513

Merged
merged 12 commits into from
Oct 13, 2021
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import * as React from 'react'
import { ALIGN_START, Flex } from '@opentrons/components'
import { LabwarePositionCheckStepDetail } from './LabwarePositionCheckStepDetail'
import { PositionCheckNav } from './PositionCheckNav'
import type { LabwarePositionCheckStep } from './types'
import { useIntroInfo } from './hooks'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the type import should go last


interface GenericStepScreenProps {
selectedStep: LabwarePositionCheckStep
setCurrentLabwareCheckStep: (stepNumber: number) => void
}
export const GenericStepScreen = (
export function GenericStepScreen(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come this got changed from a function expression to a function declaration?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this is outdated now, ignore! 😄

props: GenericStepScreenProps
): JSX.Element | null => {
return <LabwarePositionCheckStepDetail selectedStep={props.selectedStep} />
): JSX.Element | null {
const introInfo = useIntroInfo()
const [sectionIndex] = React.useState<number>(0)
if (introInfo == null) return null
const { sections, primaryPipetteMount, secondaryPipetteMount } = introInfo

return (
<React.Fragment>
<Flex alignItems={ALIGN_START}>
<PositionCheckNav
primaryPipetteMount={primaryPipetteMount}
secondaryPipetteMount={secondaryPipetteMount}
sections={sections}
currentSection={sections[sectionIndex]}
/>
<Flex>
<LabwarePositionCheckStepDetail selectedStep={props.selectedStep} />
</Flex>
</Flex>
</React.Fragment>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const LabwarePositionCheckStepDetail = (
width="60%"
justifyContent={JUSTIFY_CENTER}
marginTop={SPACING_4}
marginLeft="30%"
marginLeft="35%"
boxShadow="1px 1px 1px rgba(0, 0, 0, 0.25)"
borderRadius="4px"
backgroundColor={C_NEAR_WHITE}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import capitalize from 'lodash/capitalize'

import {
Flex,
Box,
Expand All @@ -15,13 +14,18 @@ import {
TEXT_ALIGN_CENTER,
FONT_SIZE_CAPTION,
Text,
C_DISABLED,
Icon,
SPACING_3,
COLOR_SUCCESS,
} from '@opentrons/components'
import type { Section } from './types'
interface Props {
sections: Section[]
currentSection: Section
primaryPipetteMount: string
secondaryPipetteMount: string
completedSections?: Section[]
}

export function PositionCheckNav(props: Props): JSX.Element {
Expand All @@ -30,45 +34,77 @@ export function PositionCheckNav(props: Props): JSX.Element {
sections,
primaryPipetteMount,
secondaryPipetteMount,
completedSections,
} = props
const { t } = useTranslation('labware_position_check')

return (
<Box
fontSize={FONT_SIZE_CAPTION}
padding={SPACING_2}
width="13.25rem"
padding={SPACING_3}
width="14rem"
marginLeft={SPACING_5}
boxShadow="1px 1px 1px rgba(0, 0, 0, 0.25)"
borderRadius="4px"
backgroundColor={C_NEAR_WHITE}
>
{sections.map((section, index) => (
<Flex key={index} padding={SPACING_2} alignItems={ALIGN_CENTER}>
<Box
width={SIZE_1}
height={SIZE_1}
lineHeight={SIZE_1}
backgroundColor={
section === currentSection ? '#00c3e6' : C_DARK_GRAY
}
color={C_WHITE}
borderRadius="50%"
marginRight={SPACING_2}
textAlign={TEXT_ALIGN_CENTER}
>
{index + 1}
</Box>
<Box maxWidth="85%">
<Text color={section === currentSection ? '#00c3e6' : C_DARK_GRAY}>
{t(`${section.toLowerCase()}_section`, {
primary_mount: capitalize(primaryPipetteMount),
secondary_mount: capitalize(secondaryPipetteMount),
})}
</Text>
</Box>
</Flex>
))}
{sections.map((section, index) => {
const sectionTextOrBackgroundColor =
completedSections != null && !completedSections.includes(section)
? C_DISABLED
: C_DARK_GRAY
const isCompleted =
completedSections != null && completedSections.includes(section)
let iconBackgroundColor = C_DISABLED
if (section === currentSection) {
iconBackgroundColor = '#00c3e6'
} else if (isCompleted === true) {
iconBackgroundColor = 'transparent'
} else {
iconBackgroundColor = sectionTextOrBackgroundColor
}
return (
<Flex key={index} padding={SPACING_2} alignItems={ALIGN_CENTER}>
<Box
width={SIZE_1}
height={SIZE_1}
lineHeight={SIZE_1}
backgroundColor={iconBackgroundColor}
color={C_WHITE}
borderRadius="50%"
marginRight={SPACING_2}
textAlign={TEXT_ALIGN_CENTER}
>
{isCompleted ? (
<Icon
name="check-circle"
width={SIZE_1}
height={SIZE_1}
lineHeight={SIZE_1}
marginRight={SPACING_2}
color={COLOR_SUCCESS}
/>
) : (
index + 1
)}
</Box>
<Box maxWidth="85%">
<Text
color={
section === currentSection
? '#00c3e6'
: sectionTextOrBackgroundColor
}
>
{t(`${section.toLowerCase()}_section`, {
primary_mount: capitalize(primaryPipetteMount),
secondary_mount: capitalize(secondaryPipetteMount),
})}
</Text>
</Box>
</Flex>
)
})}
</Box>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,29 @@ import {
import { i18n } from '../../../../i18n'
import { GenericStepScreen } from '../GenericStepScreen'
import { LabwarePositionCheckStepDetail } from '../LabwarePositionCheckStepDetail'
import { PositionCheckNav } from '../PositionCheckNav'
import { useIntroInfo } from '../hooks'
import { Section } from '../types'

jest.mock('../LabwarePositionCheckStepDetail')
jest.mock('../PositionCheckNav')
jest.mock('../hooks')

const mockLabwarePositionCheckStepDetail = LabwarePositionCheckStepDetail as jest.MockedFunction<
typeof LabwarePositionCheckStepDetail
>
const mockPositionCheckNav = PositionCheckNav as jest.MockedFunction<
typeof PositionCheckNav
>
const mockUseIntroInfo = useIntroInfo as jest.MockedFunction<
typeof useIntroInfo
>

const PICKUP_TIP_LABWARE_ID = 'PICKUP_TIP_LABWARE_ID'
const PRIMARY_PIPETTE_ID = 'PRIMARY_PIPETTE_ID'
const mockLabwarePositionCheckStepTipRack = {
const MOCK_SECTION = ['PRIMARY_PIPETTE_TIPRACKS' as Section]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this variable represents multiple sections, can we call it MOCK_SECTIONS (plural)?


const MOCK_LABWARE_POSITION_CHECK_STEP_TIPRACK = {
labwareId:
'1d57fc10-67ad-11ea-9f8b-3b50068bd62d:opentrons/opentrons_96_filtertiprack_200ul/1',
section: '',
Expand All @@ -41,19 +55,39 @@ describe('GenericStepScreen', () => {

beforeEach(() => {
props = {
selectedStep: mockLabwarePositionCheckStepTipRack,
selectedStep: MOCK_LABWARE_POSITION_CHECK_STEP_TIPRACK,
setCurrentLabwareCheckStep: () => {},
}
when(mockLabwarePositionCheckStepDetail)
.calledWith(
partialComponentPropsMatcher({
selectedStep: mockLabwarePositionCheckStepTipRack,
selectedStep: MOCK_LABWARE_POSITION_CHECK_STEP_TIPRACK,
})
)
.mockReturnValue(<div>Mock Labware Position Check Step Detail</div>)

mockPositionCheckNav.mockReturnValue(<div>Mock Position Check Nav</div>)
when(mockUseIntroInfo).calledWith().mockReturnValue({
primaryTipRackSlot: '1',
primaryTipRackName: 'Opentrons 96 Filter Tip Rack 200 µL',
primaryPipetteMount: 'left',
secondaryPipetteMount: '',
numberOfTips: 1,
firstStepLabwareSlot: '2',
sections: MOCK_SECTION,
})
})
it('renders LabwarePositionCheckStepDetail component', () => {
const { getByText } = render(props)
expect(getByText('Mock Labware Position Check Step Detail')).toBeTruthy()
})
it('renders GenericStepScreenNav component', () => {
const { getByText } = render(props)
expect(getByText('Mock Position Check Nav')).toBeTruthy()
})
it('renders null if useIntroInfo is null', () => {
mockUseIntroInfo.mockReturnValue(null)
const { container } = render(props)
expect(container.firstChild).toBeNull()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jest.mock('../hooks')
const mockGenericStepScreen = GenericStepScreen as jest.MockedFunction<
typeof GenericStepScreen
>

const mockIntroScreen = IntroScreen as jest.MockedFunction<typeof IntroScreen>
const mockUseSteps = useSteps as jest.MockedFunction<typeof useSteps>

Expand Down Expand Up @@ -53,12 +54,14 @@ describe('LabwarePositionCheck', () => {
} as LabwarePositionCheckStep,
])
mockIntroScreen.mockReturnValue(<div>Mock Intro Screen Component </div>)

mockGenericStepScreen.mockReturnValue(null)
})
afterEach(() => {
resetAllWhenMocks()
jest.resetAllMocks()
})

it('renders LabwarePositionCheck header and button and no components', () => {
const { getByRole } = render(props)
getByRole('heading', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { renderWithProviders } from '@opentrons/components'
import * as React from 'react'
import { i18n } from '../../../../i18n'
import { PositionCheckNav } from '../PositionCheckNav'
import { Section } from '../types'

const MOCK_SECTIONS_1_PIPETTE_2_STEPS = [
'PRIMARY_PIPETTE_TIPRACKS',
'RETURN_TIP',
] as Section[]
const MOCK_SECTIONS_2_PIPETTES_3_STEPS = [
'PRIMARY_PIPETTE_TIPRACKS',
'SECONDARY_PIPETTE_TIPRACKS',
'RETURN_TIP',
] as Section[]
const MOCK_SECTIONS_2_PIPETTES_4_STEPS = [
'PRIMARY_PIPETTE_TIPRACKS',
'SECONDARY_PIPETTE_TIPRACKS',
'CHECK_REMAINING_LABWARE_WITH_PRIMARY_PIPETTE',
'RETURN_TIP',
] as Section[]

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

describe('PositionCheckNav', () => {
let props: React.ComponentProps<typeof PositionCheckNav>

beforeEach(() => {
props = {
sections: MOCK_SECTIONS_1_PIPETTE_2_STEPS,
currentSection: 'PRIMARY_PIPETTE_TIPRACKS',
primaryPipetteMount: 'left',
secondaryPipetteMount: '',
}
})
it('renders a 2 step Nav with 1 pipette', () => {
const { getByText } = render(props)
expect(getByText('1')).toHaveStyle('backgroundColor: #00c3e6')
expect(getByText('Check tipracks with Left Pipette')).toBeTruthy()
expect(getByText('2')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Return tip')).toBeTruthy()
})
it('renders a 3 step Nav with 2 pipettes', () => {
props = {
sections: MOCK_SECTIONS_2_PIPETTES_3_STEPS,
currentSection: 'PRIMARY_PIPETTE_TIPRACKS',
primaryPipetteMount: 'left',
secondaryPipetteMount: 'right',
}
const { getByText } = render(props)
expect(getByText('1')).toHaveStyle('backgroundColor: #00c3e6')
expect(getByText('Check tipracks with Left Pipette')).toBeTruthy()
expect(getByText('2')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Check tipracks with Right Pipette')).toBeTruthy()
expect(getByText('3')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Return tip')).toBeTruthy()
})
it('renders a 4 step Nav with 2 pipettes', () => {
props = {
sections: MOCK_SECTIONS_2_PIPETTES_4_STEPS,
currentSection: 'PRIMARY_PIPETTE_TIPRACKS',
primaryPipetteMount: 'left',
secondaryPipetteMount: 'right',
}
const { getByText } = render(props)
expect(getByText('1')).toHaveStyle('backgroundColor: #00c3e6')
expect(getByText('Check tipracks with Left Pipette')).toBeTruthy()
expect(getByText('2')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Check tipracks with Right Pipette')).toBeTruthy()
expect(getByText('3')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Check remaining labware with Left Pipette')).toBeTruthy()
expect(getByText('4')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Return tip')).toBeTruthy()
})
it('renders a 3 step Nav with 2 pipettes and on the second step', () => {
props = {
sections: MOCK_SECTIONS_2_PIPETTES_3_STEPS,
currentSection: 'SECONDARY_PIPETTE_TIPRACKS',
primaryPipetteMount: 'left',
secondaryPipetteMount: 'right',
completedSections: ['PRIMARY_PIPETTE_TIPRACKS'],
}
const { getByText } = render(props)
expect(getByText('Check tipracks with Left Pipette')).toBeTruthy()
expect(getByText('2')).toHaveStyle('backgroundColor: #00c3e6')
expect(getByText('Check tipracks with Right Pipette')).toBeTruthy()
expect(getByText('3')).toHaveStyle('backgroundColor: C_DISABLED')
expect(getByText('Return tip')).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export const LabwarePositionCheck = (
const [currentLabwareCheckStep, setCurrentLabwareCheckStep] = React.useState<
number | null
>(null)
// placeholder for next steps
console.log(currentLabwareCheckStep)

return (
<Portal level="top">
Expand Down