Skip to content

Commit

Permalink
feat(protocol-designer): prevent user from adding plate reader withou…
Browse files Browse the repository at this point in the history
…t gripper (#17152)

Here, I prevent saving adding a plate reader in DeckSetupTools if a
gripper has not already been added. In a followup, we will enforce a
timeline error at for plate reader stepforms that modify the lid state
of the reader.

TODO: update snackbar copy/confirm that behavior with Design

Closes AUTH-1095
  • Loading branch information
ncdiehl11 authored Dec 19, 2024
1 parent a51b202 commit 81a90e4
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"edit_slot": "Edit slot",
"edit": "Edit",
"gen1_gen2_different_units": "Switching between GEN1 and GEN2 Magnetic Modules will clear all non-default engage heights from existing magnet steps in your protocol. GEN1 and GEN2 Magnetic Modules do not use the same units.",
"gripper_required_for_plate_reader": "Gripper required to add absorbance reader",
"heater_shaker_adjacent_to": "A module is adjacent to this slot. The Heater-Shaker cannot be placed next to a module",
"heater_shaker_adjacent": "A Heater-Shaker is adjacent to this slot. Modules cannot be placed next to a Heater-Shaker",
"heater_shaker_trash": "The heater-shaker cannot be next to the trash bin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
WRAP,
} from '@opentrons/components'
import {
ABSORBANCE_READER_TYPE,
ABSORBANCE_READER_V1,
FLEX_ROBOT_TYPE,
getModuleDisplayName,
Expand Down Expand Up @@ -70,6 +71,7 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null {
TEMPERATURE_MODULE_TYPE,
HEATERSHAKER_MODULE_TYPE,
MAGNETIC_BLOCK_TYPE,
ABSORBANCE_READER_TYPE,
]

const handleAddModule = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
TYPOGRAPHY,
} from '@opentrons/components'
import {
ABSORBANCE_READER_TYPE,
ABSORBANCE_READER_V1,
FLEX_ROBOT_TYPE,
FLEX_STAGING_AREA_SLOT_ADDRESSABLE_AREAS,
Expand All @@ -36,6 +37,7 @@ import {
deleteDeckFixture,
} from '../../../step-forms/actions/additionalItems'
import { createModule, deleteModule } from '../../../step-forms/actions'
import { getAdditionalEquipment } from '../../../step-forms/selectors'
import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations'
import {
createContainer,
Expand Down Expand Up @@ -110,6 +112,10 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
const [changeModuleWarningInfo, displayModuleWarning] = useState<boolean>(
false
)
const additionalEquipment = useSelector(getAdditionalEquipment)
const isGripperAttached = Object.values(additionalEquipment).some(
equipment => equipment?.name === 'gripper'
)
const [selectedHardware, setSelectedHardware] = useState<
ModuleModel | Fixture | null
>(null)
Expand Down Expand Up @@ -320,10 +326,16 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
}
if (selectedModuleModel != null) {
// create module
const moduleType = getModuleType(selectedModuleModel)
// enforce gripper present in order to add plate reader
if (moduleType === ABSORBANCE_READER_TYPE && !isGripperAttached) {
makeSnackbar(t('gripper_required_for_plate_reader') as string)
return
}
dispatch(
createModule({
slot,
type: getModuleType(selectedModuleModel),
type: moduleType,
model: selectedModuleModel,
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import '@testing-library/jest-dom/vitest'
import { fireEvent, screen } from '@testing-library/react'
import {
ABSORBANCE_READER_V1,
FLEX_ROBOT_TYPE,
HEATERSHAKER_MODULE_V1,
fixture96Plate,
} from '@opentrons/shared-data'
import { i18n } from '../../../../assets/localization'
import { renderWithProviders } from '../../../../__testing-utils__'
import { deleteContainer } from '../../../../labware-ingred/actions'
import { useKitchen } from '../../../../organisms/Kitchen/hooks'
import { deleteModule } from '../../../../step-forms/actions'
import { getAdditionalEquipment } from '../../../../step-forms/selectors'
import { getRobotType } from '../../../../file-data/selectors'
import { getEnableAbsorbanceReader } from '../../../../feature-flags/selectors'
import { deleteDeckFixture } from '../../../../step-forms/actions/additionalItems'
Expand All @@ -31,12 +34,16 @@ vi.mock('../../../../step-forms/actions')
vi.mock('../../../../step-forms/actions/additionalItems')
vi.mock('../../../../labware-ingred/selectors')
vi.mock('../../../../tutorial/selectors')
vi.mock('../../../../step-forms/selectors')
vi.mock('../../../../organisms/Kitchen/hooks')
const render = (props: React.ComponentProps<typeof DeckSetupTools>) => {
return renderWithProviders(<DeckSetupTools {...props} />, {
i18nInstance: i18n,
})[0]
}

const mockMakeSnackbar = vi.fn()

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

Expand Down Expand Up @@ -66,6 +73,12 @@ describe('DeckSetupTools', () => {
pipettes: {},
})
vi.mocked(getDismissedHints).mockReturnValue([])
vi.mocked(getAdditionalEquipment).mockReturnValue({})
vi.mocked(useKitchen).mockReturnValue({
makeSnackbar: mockMakeSnackbar,
bakeToast: vi.fn(),
eatToast: vi.fn(),
})
})
afterEach(() => {
vi.resetAllMocks()
Expand Down Expand Up @@ -164,4 +177,32 @@ describe('DeckSetupTools', () => {
fireEvent.click(screen.getByText('Done'))
expect(props.onCloseClick).toHaveBeenCalled()
})
it('should save plate reader if gripper configured', () => {
vi.mocked(getAdditionalEquipment).mockReturnValue({
gripperUri: { name: 'gripper', id: 'gripperId' },
})
vi.mocked(selectors.getZoomedInSlotInfo).mockReturnValue({
selectedLabwareDefUri: null,
selectedNestedLabwareDefUri: null,
selectedFixture: null,
selectedModuleModel: ABSORBANCE_READER_V1,
selectedSlot: { slot: 'D3', cutout: 'cutoutD3' },
})
render(props)
fireEvent.click(screen.getByText('Done'))
expect(props.onCloseClick).toHaveBeenCalled()
})
it('should prevent saving plate reader and make toast if gripper not configured', () => {
vi.mocked(selectors.getZoomedInSlotInfo).mockReturnValue({
selectedLabwareDefUri: null,
selectedNestedLabwareDefUri: null,
selectedFixture: null,
selectedModuleModel: ABSORBANCE_READER_V1,
selectedSlot: { slot: 'D3', cutout: 'cutoutD3' },
})
render(props)
fireEvent.click(screen.getByText('Done'))
expect(props.onCloseClick).not.toHaveBeenCalled()
expect(mockMakeSnackbar).toHaveBeenCalled()
})
})

0 comments on commit 81a90e4

Please sign in to comment.