Skip to content

Commit

Permalink
fix(protocol-designer): fix pipette info card in protocol overview (#…
Browse files Browse the repository at this point in the history
…16429)

* fix(protocol-designer): fix pipette info card in protocol overview
  • Loading branch information
koji authored Oct 8, 2024
1 parent 44aa757 commit 7e39d06
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 81 deletions.
130 changes: 130 additions & 0 deletions protocol-designer/src/pages/ProtocolOverview/InstrumentsInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { useTranslation } from 'react-i18next'

import {
Flex,
StyledText,
Btn,
DIRECTION_COLUMN,
SPACING,
JUSTIFY_SPACE_BETWEEN,
TYPOGRAPHY,
ListItem,
ListItemDescriptor,
} from '@opentrons/components'
import { getPipetteSpecsV2, FLEX_ROBOT_TYPE } from '@opentrons/shared-data'

import { BUTTON_LINK_STYLE } from '../../atoms'

import type { PipetteName, RobotType } from '@opentrons/shared-data'
import type { AdditionalEquipmentEntities } from '@opentrons/step-generation'
import type { PipetteOnDeck } from '../../step-forms'

interface InstrumentsInfoProps {
robotType: RobotType
pipettesOnDeck: PipetteOnDeck[]
additionalEquipment: AdditionalEquipmentEntities
setShowEditInstrumentsModal: (showEditInstrumentsModal: boolean) => void
}

export function InstrumentsInfo({
robotType,
pipettesOnDeck,
additionalEquipment,
setShowEditInstrumentsModal,
}: InstrumentsInfoProps): JSX.Element {
const { t } = useTranslation(['protocol_overview', 'shared'])
const leftPipette = pipettesOnDeck.find(pipette => pipette.mount === 'left')
const rightPipette = pipettesOnDeck.find(pipette => pipette.mount === 'right')
const isGripperAttached = Object.values(additionalEquipment).some(
equipment => equipment?.name === 'gripper'
)

const pipetteInfo = (pipette?: PipetteOnDeck): JSX.Element | string => {
const pipetteName =
pipette != null
? getPipetteSpecsV2(pipette.name as PipetteName)?.displayName
: t('na')
const tipsInfo = pipette?.tiprackLabwareDef
? pipette.tiprackLabwareDef.map(labware => labware.metadata.displayName)
: t('na')

if (pipetteName === t('na') || tipsInfo === t('na')) {
return t('na')
}

return (
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultRegular">{pipetteName}</StyledText>
{pipette != null && pipette.tiprackLabwareDef.length > 0
? pipette?.tiprackLabwareDef.map(labware => (
<StyledText
key={labware.metadata.displayName}
desktopStyle="bodyDefaultRegular"
>
{labware.metadata.displayName}
</StyledText>
))
: null}
</Flex>
)
}

return (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing12}>
<Flex justifyContent={JUSTIFY_SPACE_BETWEEN}>
<StyledText desktopStyle="headingSmallBold">
{t('instruments')}
</StyledText>
<Flex padding={SPACING.spacing4}>
<Btn
textDecoration={TYPOGRAPHY.textDecorationUnderline}
onClick={() => {
setShowEditInstrumentsModal(true)
}}
css={BUTTON_LINK_STYLE}
>
<StyledText desktopStyle="bodyDefaultRegular">
{t('edit')}
</StyledText>
</Btn>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<ListItem type="noActive" key={`ProtocolOverview_robotType`}>
<ListItemDescriptor
type="default"
description={t('robotType')}
content={
robotType === FLEX_ROBOT_TYPE
? t('shared:opentrons_flex')
: t('shared:ot2')
}
/>
</ListItem>
<ListItem type="noActive" key={`ProtocolOverview_left`}>
<ListItemDescriptor
type="default"
description={t('left_pip')}
content={pipetteInfo(leftPipette)}
/>
</ListItem>
<ListItem type="noActive" key={`ProtocolOverview_right`}>
<ListItemDescriptor
type="default"
description={t('right_pip')}
content={pipetteInfo(rightPipette)}
/>
</ListItem>
{robotType === FLEX_ROBOT_TYPE ? (
<ListItem type="noActive" key={`ProtocolOverview_gripper`}>
<ListItemDescriptor
type="default"
description={t('extension')}
content={isGripperAttached ? t('gripper') : t('na')}
/>
</ListItem>
) : null}
</Flex>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, it, vi, beforeEach, expect } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'

import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'

import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../assets/localization'
import { InstrumentsInfo } from '../InstrumentsInfo'

import type { ComponentProps } from 'react'
import type { AdditionalEquipmentEntities } from '@opentrons/step-generation'
import type { PipetteOnDeck } from '../../../step-forms'

const mockSetShowEditInstrumentsModal = vi.fn()
const mockPipettes = [
{
mount: 'left',
id: 'mock-left',
name: 'p50_single_flex',
tiprackDefURI: ['opentrons/opentrons_flex_96_tiprack_50ul/1'],
tiprackLabwareDef: [
{
metadata: {
displayName: 'Opentrons Flex 96 Tip Rack 50 µL',
displayCategory: 'tipRack',
displayVolumeUnits: 'µL',
tags: [],
},
} as any,
],
} as PipetteOnDeck,
{
mount: 'right',
id: 'mock-right',
name: 'p50_multi_flex',
tiprackDefURI: ['opentrons/opentrons_flex_96_filtertiprack_50ul/1'],
tiprackLabwareDef: [
{
metadata: {
displayName: 'Opentrons Flex 96 Filter Tip Rack 50 µL',
displayCategory: 'tipRack',
displayVolumeUnits: 'µL',
tags: [],
},
} as any,
],
} as PipetteOnDeck,
]

const mockAdditionalEquipment = {
'mock:gripper': {
name: 'gripper',
id: 'mock:gripper',
},
} as AdditionalEquipmentEntities

const render = (props: ComponentProps<typeof InstrumentsInfo>) => {
return renderWithProviders(<InstrumentsInfo {...props} />, {
i18nInstance: i18n,
})
}

describe('InstrumentsInfo', () => {
let props: ComponentProps<typeof InstrumentsInfo>

beforeEach(() => {
props = {
robotType: FLEX_ROBOT_TYPE,
pipettesOnDeck: [],
additionalEquipment: {},
setShowEditInstrumentsModal: mockSetShowEditInstrumentsModal,
}
})

it('should render text', () => {
render(props)
screen.getByText('Instruments')
screen.getByText('Robot type')
screen.getAllByText('Opentrons Flex')
screen.getByText('Left pipette')
screen.getByText('Right pipette')
screen.getByText('Extension mount')
expect(screen.getAllByText('N/A').length).toBe(3)
})

it('should render instruments info', () => {
props = {
...props,
pipettesOnDeck: mockPipettes,
additionalEquipment: mockAdditionalEquipment,
}
render(props)

screen.getByText('Flex 1-Channel 50 μL')
screen.getByText('Opentrons Flex 96 Tip Rack 50 µL')
screen.getByText('Flex 8-Channel 50 μL')
screen.getByText('Opentrons Flex 96 Filter Tip Rack 50 µL')
screen.getByText('Opentrons Flex Gripper')
})

it('should call mock function when clicking edit text button', () => {
render(props)
fireEvent.click(screen.getByText('Edit'))
expect(mockSetShowEditInstrumentsModal).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { selectors as labwareIngredSelectors } from '../../../labware-ingred/sel
import { ProtocolOverview } from '../index'
import { DeckThumbnail } from '../DeckThumbnail'
import { OffDeckThumbnail } from '../OffdeckThumbnail'
import { InstrumentsInfo } from '../InstrumentsInfo'
import { LiquidDefinitions } from '../LiquidDefinitions'

import type { NavigateFunction } from 'react-router-dom'
Expand All @@ -29,6 +30,7 @@ vi.mock('../../../labware-ingred/selectors')
vi.mock('../../../organisms')
vi.mock('../../../labware-ingred/selectors')
vi.mock('../LiquidDefinitions')
vi.mock('../InstrumentsInfo')

const mockNavigate = vi.fn()

Expand Down Expand Up @@ -78,6 +80,7 @@ describe('ProtocolOverview', () => {
vi.mocked(LiquidDefinitions).mockReturnValue(
<div>mock LiquidDefinitions</div>
)
vi.mocked(InstrumentsInfo).mockReturnValue(<div>mock InstrumentsInfo</div>)
})

it('renders each section with text', () => {
Expand All @@ -99,15 +102,13 @@ describe('ProtocolOverview', () => {
screen.getByText('Last exported')
screen.getByText('Required app version')
screen.getByText('8.0.0 or higher')

// instruments
screen.getByText('Instruments')
screen.getByText('Robot type')
screen.getAllByText('Opentrons Flex')
screen.getByText('Left pipette')
screen.getByText('Right pipette')
screen.getByText('Extension mount')
screen.getByText('mock InstrumentsInfo')

// liquids
screen.getByText('mock LiquidDefinitions')

// steps
screen.getByText('Protocol steps')
})
Expand All @@ -126,7 +127,8 @@ describe('ProtocolOverview', () => {
description: undefined,
})
render()
expect(screen.getAllByText('N/A').length).toBe(7)
// ToDo (kk: 2024/10/07) this part should be replaced
expect(screen.getAllByText('N/A').length).toBe(4)
})

it('navigates to starting deck state', () => {
Expand Down
Loading

0 comments on commit 7e39d06

Please sign in to comment.