From 3a27f066efc20d8b48b3306f973a70d274dc06c7 Mon Sep 17 00:00:00 2001 From: Jethary Alcid <66035149+jerader@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:46:01 -0400 Subject: [PATCH] feat(protocol-designer): deck setup skeleton for redesign (#15899) closes AUTH-635 --- .../src/components/DeckSetup/index.tsx | 2 +- .../src/components/DeckSetupManager.tsx | 4 +- .../localization/en/protocol_overview.json | 2 + .../DeckSetup/ControlSelect.tsx | 89 +++++ .../DeckSetup/DeckSetup.module.css | 25 ++ .../DeckSetup/DeckSetupDetails.tsx | 358 ++++++++++++++++++ .../DeckSetup/SlotDetailsContainer.tsx | 30 ++ .../ProtocolOverview/DeckSetup/index.tsx | 258 +++++++++++++ .../pages/ProtocolOverview/DeckSetup/utils.ts | 20 + .../__tests__/ProtocolOverview.test.tsx | 27 +- .../src/pages/ProtocolOverview/index.tsx | 25 +- 11 files changed, 834 insertions(+), 6 deletions(-) create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/ControlSelect.tsx create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetup.module.css create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetupDetails.tsx create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/SlotDetailsContainer.tsx create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/index.tsx create mode 100644 protocol-designer/src/pages/ProtocolOverview/DeckSetup/utils.ts diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx index 4ca74461909..736753e715a 100644 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ b/protocol-designer/src/components/DeckSetup/index.tsx @@ -479,7 +479,7 @@ export const DeckSetupContents = (props: ContentsProps): JSX.Element => { ) } -export const DeckSetup = (): JSX.Element => { +export const LegacyDeckSetup = (): JSX.Element => { const drilledDown = useSelector(labwareIngredSelectors.getDrillDownLabwareId) != null const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) diff --git a/protocol-designer/src/components/DeckSetupManager.tsx b/protocol-designer/src/components/DeckSetupManager.tsx index 8aabeba5090..2c065b7a545 100644 --- a/protocol-designer/src/components/DeckSetupManager.tsx +++ b/protocol-designer/src/components/DeckSetupManager.tsx @@ -4,7 +4,7 @@ import { getBatchEditSelectedStepTypes, getHoveredItem, } from '../ui/steps/selectors' -import { DeckSetup } from './DeckSetup' +import { LegacyDeckSetup } from './DeckSetup' import { NullDeckState } from './DeckSetup/NullDeckState' import { OffDeckLabwareButton } from './OffDeckLabwareButton' @@ -17,7 +17,7 @@ export const DeckSetupManager = (): JSX.Element => { return ( <> - + ) } else { diff --git a/protocol-designer/src/localization/en/protocol_overview.json b/protocol-designer/src/localization/en/protocol_overview.json index 475116fc440..1ffb61db81d 100644 --- a/protocol-designer/src/localization/en/protocol_overview.json +++ b/protocol-designer/src/localization/en/protocol_overview.json @@ -1,3 +1,5 @@ { + "add_labware": "Add labware", + "edit": "Edit", "protocol_overview": "Protocol overview" } diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/ControlSelect.tsx b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/ControlSelect.tsx new file mode 100644 index 00000000000..a12f6e32dff --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/ControlSelect.tsx @@ -0,0 +1,89 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import cx from 'classnames' +import { css } from 'styled-components' +import { useSelector } from 'react-redux' +import { + Flex, + LegacyStyledText, + RobotCoordsForeignDiv, +} from '@opentrons/components' +import { START_TERMINAL_ITEM_ID } from '../../../steplist' +import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' + +import type { CoordinateTuple, Dimensions } from '@opentrons/shared-data' +import type { TerminalItemId } from '../../../steplist' + +import styles from './DeckSetup.module.css' + +interface ControlSelectProps { + slotPosition: CoordinateTuple | null + slotBoundingBox: Dimensions + slotId: string + addEquipment: (slotId: string) => void + hover: string | null + setHover: React.Dispatch> + slotTopLayerId: string // can be AddressableAreaName, moduleId, labwareId + selectedTerminalItemId?: TerminalItemId | null +} + +export const ControlSelect = ( + props: ControlSelectProps +): JSX.Element | null => { + const { + slotBoundingBox, + slotPosition, + slotId, + selectedTerminalItemId, + addEquipment, + hover, + setHover, + slotTopLayerId, + } = props + const { t } = useTranslation('protocol_overview') + const activeDeckSetup = useSelector(getDeckSetupForActiveItem) + const moduleId = Object.keys(activeDeckSetup.modules).find( + moduleId => slotTopLayerId === moduleId + ) + + if (selectedTerminalItemId !== START_TERMINAL_ITEM_ID || slotPosition == null) + return null + + return ( + { + setHover(slotId) + }, + onMouseLeave: () => { + setHover(null) + }, + onClick: () => { + addEquipment(slotId) + }, + }} + > + + { + addEquipment(slotId) + }} + > + + {moduleId != null ? t('add_labware') : t('edit')} + + + + + ) +} diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetup.module.css b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetup.module.css new file mode 100644 index 00000000000..b4b88d3a8f8 --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetup.module.css @@ -0,0 +1,25 @@ +.slot_overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + padding: 0.5rem; + background-color: color-mod(var(--c-black) alpha(0.75)); + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: flex-start; + color: white; + font-size: var(--fs-body-1); + border-radius: 0.5rem; +} + +.appear_on_mouseover { + opacity: 0; + + &:hover { + opacity: 1; + } +} diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetupDetails.tsx b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetupDetails.tsx new file mode 100644 index 00000000000..ddf4e25897e --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/DeckSetupDetails.tsx @@ -0,0 +1,358 @@ +import * as React from 'react' +import compact from 'lodash/compact' +import values from 'lodash/values' + +import { Module } from '@opentrons/components' +import { MODULES_WITH_COLLISION_ISSUES } from '@opentrons/step-generation' +import { + getAddressableAreaFromSlotId, + getLabwareHasQuirk, + getModuleDef2, + getPositionFromSlotId, + inferModuleOrientationFromSlot, + inferModuleOrientationFromXCoordinate, + isAddressableAreaStandardSlot, + SPAN7_8_10_11_SLOT, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' +import { + getSlotIdsBlockedBySpanning, + getSlotIsEmpty, +} from '../../../step-forms' +import { LabwareOnDeck } from '../../../components/DeckSetup/LabwareOnDeck' +import { SlotWarning } from '../../../components/DeckSetup/SlotWarning' +import { getStagingAreaAddressableAreas } from '../../../utils' +import { ControlSelect } from './ControlSelect' + +import type { ModuleTemporalProperties } from '@opentrons/step-generation' +import type { + AddressableAreaName, + CutoutId, + DeckDefinition, + Dimensions, +} from '@opentrons/shared-data' +import type { + InitialDeckSetup, + LabwareOnDeck as LabwareOnDeckType, + ModuleOnDeck, +} from '../../../step-forms' +import type { TerminalItemId } from '../../../steplist' + +interface DeckSetupDetailsProps { + activeDeckSetup: InitialDeckSetup + showGen1MultichannelCollisionWarnings: boolean + deckDef: DeckDefinition + stagingAreaCutoutIds: CutoutId[] + trashSlot: string | null + addEquipment: (slotId: string) => void + hover: string | null + setHover: React.Dispatch> + selectedTerminalItemId?: TerminalItemId | null +} + +export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { + const { + activeDeckSetup, + showGen1MultichannelCollisionWarnings, + deckDef, + trashSlot, + addEquipment, + stagingAreaCutoutIds, + selectedTerminalItemId, + hover, + setHover, + } = props + const slotIdsBlockedBySpanning = getSlotIdsBlockedBySpanning(activeDeckSetup) + + const allLabware: LabwareOnDeckType[] = Object.keys( + activeDeckSetup.labware + ).reduce((acc, labwareId) => { + const labware = activeDeckSetup.labware[labwareId] + return getLabwareHasQuirk(labware.def, 'fixedTrash') + ? acc + : [...acc, labware] + }, []) + + const allModules: ModuleOnDeck[] = values(activeDeckSetup.modules) + + // NOTE: naively hard-coded to show warning north of slots 1 or 3 when occupied by any module + const multichannelWarningSlotIds: AddressableAreaName[] = showGen1MultichannelCollisionWarnings + ? compact([ + allModules.some( + moduleOnDeck => + moduleOnDeck.slot === '1' && + MODULES_WITH_COLLISION_ISSUES.includes(moduleOnDeck.model) + ) + ? deckDef.locations.addressableAreas.find(s => s.id === '4')?.id + : null, + allModules.some( + moduleOnDeck => + moduleOnDeck.slot === '3' && + MODULES_WITH_COLLISION_ISSUES.includes(moduleOnDeck.model) + ) + ? deckDef.locations.addressableAreas.find(s => s.id === '6')?.id + : null, + ]) + : [] + + return ( + <> + {/* all modules */} + {allModules.map(moduleOnDeck => { + const slotId = + moduleOnDeck.slot === SPAN7_8_10_11_SLOT ? '7' : moduleOnDeck.slot + + const slotPosition = getPositionFromSlotId(slotId, deckDef) + if (slotPosition == null) { + console.warn(`no slot ${slotId} for module ${moduleOnDeck.id}`) + return null + } + const moduleDef = getModuleDef2(moduleOnDeck.model) + + const getModuleInnerProps = ( + moduleState: ModuleTemporalProperties['moduleState'] + ): React.ComponentProps['innerProps'] => { + if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { + let lidMotorState = 'unknown' + if ( + selectedTerminalItemId === '__initial_setup__' || + moduleState.lidOpen === true + ) { + lidMotorState = 'open' + } else if (moduleState.lidOpen === false) { + lidMotorState = 'closed' + } + return { + lidMotorState, + blockTargetTemp: moduleState.blockTargetTemp, + } + } else if ( + 'targetTemperature' in moduleState && + moduleState.type === 'temperatureModuleType' + ) { + return { + targetTemperature: moduleState.targetTemperature, + } + } else if ('targetTemp' in moduleState) { + return { + targetTemp: moduleState.targetTemp, + } + } + } + const labwareLoadedOnModule = allLabware.find( + lw => lw.slot === moduleOnDeck.id + ) + const labwareInterfaceBoundingBox = { + xDimension: moduleDef.dimensions.labwareInterfaceXDimension ?? 0, + yDimension: moduleDef.dimensions.labwareInterfaceYDimension ?? 0, + zDimension: 0, + } + const controlSelectDimensions = { + xDimension: labwareLoadedOnModule?.def.dimensions.xDimension ?? 0, + yDimension: labwareLoadedOnModule?.def.dimensions.yDimension ?? 0, + zDimension: labwareLoadedOnModule?.def.dimensions.zDimension ?? 0, + } + return ( + + + {labwareLoadedOnModule != null ? ( + <> + + + + ) : null} + + {labwareLoadedOnModule == null ? ( + + ) : null} + + + ) + })} + + {/* on-deck warnings for OT-2 and GEN1 8-channels only */} + {multichannelWarningSlotIds.map(slotId => { + const slotPosition = getPositionFromSlotId(slotId, deckDef) + const slotBoundingBox = getAddressableAreaFromSlotId(slotId, deckDef) + ?.boundingBox + return slotPosition != null && slotBoundingBox != null ? ( + + ) : null + })} + + {/* SlotControls for all empty deck */} + {deckDef.locations.addressableAreas + .filter(addressableArea => { + const stagingAreaAddressableAreas = getStagingAreaAddressableAreas( + stagingAreaCutoutIds + ) + const addressableAreas = + isAddressableAreaStandardSlot(addressableArea.id, deckDef) || + stagingAreaAddressableAreas.includes(addressableArea.id) + return ( + addressableAreas && + !slotIdsBlockedBySpanning.includes(addressableArea.id) && + getSlotIsEmpty(activeDeckSetup, addressableArea.id) && + addressableArea.id !== trashSlot + ) + }) + .map(addressableArea => { + return ( + + + + ) + })} + + {/* all labware on deck NOT those in modules */} + {allLabware.map(labware => { + if ( + labware.slot === 'offDeck' || + allModules.some(m => m.id === labware.slot) || + allLabware.some(lab => lab.id === labware.slot) + ) + return null + + const slotPosition = getPositionFromSlotId(labware.slot, deckDef) + const slotBoundingBox = getAddressableAreaFromSlotId( + labware.slot, + deckDef + )?.boundingBox + if (slotPosition == null || slotBoundingBox == null) { + console.warn(`no slot ${labware.slot} for labware ${labware.id}!`) + return null + } + return ( + + + + + ) + })} + + {/* all nested labwares on deck */} + {allLabware.map(labware => { + if ( + allModules.some(m => m.id === labware.slot) || + labware.slot === 'offDeck' + ) + return null + if ( + deckDef.locations.addressableAreas.some( + addressableArea => addressableArea.id === labware.slot + ) + ) { + return null + } + const slotForOnTheDeck = allLabware.find(lab => lab.id === labware.slot) + ?.slot + const slotForOnMod = allModules.find(mod => mod.id === slotForOnTheDeck) + ?.slot + let slotPosition = null + if (slotForOnMod != null) { + slotPosition = getPositionFromSlotId(slotForOnMod, deckDef) + } else if (slotForOnTheDeck != null) { + slotPosition = getPositionFromSlotId(slotForOnTheDeck, deckDef) + } + if (slotPosition == null) { + console.warn(`no slot ${labware.slot} for labware ${labware.id}!`) + return null + } + const slotBoundingBox: Dimensions = { + xDimension: labware.def.dimensions.xDimension, + yDimension: labware.def.dimensions.yDimension, + zDimension: labware.def.dimensions.zDimension, + } + const slotOnDeck = + slotForOnTheDeck != null + ? allModules.find(module => module.id === slotForOnTheDeck)?.slot + : null + return ( + + + + + ) + })} + + ) +} diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/SlotDetailsContainer.tsx b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/SlotDetailsContainer.tsx new file mode 100644 index 00000000000..62b24855db6 --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/SlotDetailsContainer.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' + +import { + LegacyStyledText, + RobotCoordsForeignObject, +} from '@opentrons/components' +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import type { RobotType } from '@opentrons/shared-data' + +interface SlotDetailContainerProps { + robotType: RobotType +} + +export const SlotDetailsContainer = ( + props: SlotDetailContainerProps +): JSX.Element | null => { + const { robotType } = props + return ( + + {/* TODO(ja, 8/6/24): wire up slot information */} + Slot information + + ) +} diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/index.tsx b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/index.tsx new file mode 100644 index 00000000000..44048778521 --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/index.tsx @@ -0,0 +1,258 @@ +import * as React from 'react' + +import { useSelector } from 'react-redux' +import { + COLORS, + DeckFromLayers, + Flex, + FlexTrash, + LegacyStyledText, + PrimaryButton, + RobotCoordinateSpaceWithRef, + SingleSlotFixture, + SlotLabels, + StagingAreaFixture, + WasteChuteFixture, + WasteChuteStagingAreaFixture, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + getDeckDefFromRobotType, + isAddressableAreaStandardSlot, + OT2_ROBOT_TYPE, + STAGING_AREA_CUTOUTS, + TRASH_BIN_ADAPTER_FIXTURE, + WASTE_CHUTE_CUTOUT, +} from '@opentrons/shared-data' +import { getSelectedTerminalItemId } from '../../../ui/steps' +import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' +import { getDisableModuleRestrictions } from '../../../feature-flags/selectors' +import { getRobotType } from '../../../file-data/selectors' +import { getHasGen1MultiChannelPipette } from '../../../step-forms' +import { SlotDetailsContainer } from './SlotDetailsContainer' +import { DeckSetupDetails } from './DeckSetupDetails' +import { getCutoutIdForAddressableArea } from './utils' + +import type { StagingAreaLocation, TrashCutoutId } from '@opentrons/components' +import type { AddressableAreaName, CutoutId } from '@opentrons/shared-data' +import type { + AdditionalEquipmentEntity, + DeckSlot, +} from '@opentrons/step-generation' + +interface DeckSetupProps { + onCancel: () => void + onSave: () => void +} +interface OpenSlot { + cutoutId: CutoutId + slot: DeckSlot +} + +const WASTE_CHUTE_SPACE = 30 +const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [ + 'calibrationMarkings', + 'fixedBase', + 'doorStops', + 'metalFrame', + 'removalHandle', + 'removableDeckOutline', + 'screwHoles', + 'fixedTrash', +] + +const lightFill = COLORS.grey35 +const darkFill = COLORS.grey60 + +export const DeckSetup = (props: DeckSetupProps): JSX.Element => { + const { onCancel, onSave } = props + const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) + const activeDeckSetup = useSelector(getDeckSetupForActiveItem) + const _disableCollisionWarnings = useSelector(getDisableModuleRestrictions) + const robotType = useSelector(getRobotType) + const deckDef = React.useMemo(() => getDeckDefFromRobotType(robotType), []) + const [hover, setHover] = React.useState(null) + const [zoomIn, setZoomInOnSlot] = React.useState(null) + const trash = Object.values(activeDeckSetup.additionalEquipmentOnDeck).find( + ae => ae.name === 'trashBin' + ) + + const addEquipment = (slotId: string): void => { + const cutoutId = + getCutoutIdForAddressableArea( + slotId as AddressableAreaName, + deckDef.cutoutFixtures + ) ?? 'cutoutD1' + setZoomInOnSlot({ cutoutId, slot: slotId }) + } + + const trashSlot = trash?.location + + const _hasGen1MultichannelPipette = React.useMemo( + () => getHasGen1MultiChannelPipette(activeDeckSetup.pipettes), + [activeDeckSetup.pipettes] + ) + const showGen1MultichannelCollisionWarnings = + !_disableCollisionWarnings && _hasGen1MultichannelPipette + + const trashBinFixtures = [ + { + cutoutId: trash?.location as CutoutId, + cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE, + }, + ] + const wasteChuteFixtures = Object.values( + activeDeckSetup.additionalEquipmentOnDeck + ).filter( + aE => + WASTE_CHUTE_CUTOUT.includes(aE.location as CutoutId) && + aE.name === 'wasteChute' + ) + const stagingAreaFixtures: AdditionalEquipmentEntity[] = Object.values( + activeDeckSetup.additionalEquipmentOnDeck + ).filter( + aE => + STAGING_AREA_CUTOUTS.includes(aE.location as CutoutId) && + aE.name === 'stagingArea' + ) + + const wasteChuteStagingAreaFixtures = Object.values( + activeDeckSetup.additionalEquipmentOnDeck + ).filter( + aE => + STAGING_AREA_CUTOUTS.includes(aE.location as CutoutId) && + aE.name === 'stagingArea' && + aE.location === WASTE_CHUTE_CUTOUT && + wasteChuteFixtures.length > 0 + ) + + const hasWasteChute = + wasteChuteFixtures.length > 0 || wasteChuteStagingAreaFixtures.length > 0 + + const filteredAddressableAreas = deckDef.locations.addressableAreas.filter( + aa => isAddressableAreaStandardSlot(aa.id, deckDef) + ) + return ( + <> + cancel + save + {zoomIn != null ? ( + // TODO(ja, 8/6/24): still need to develop the zoomed in slot + you zoomed in on the slot! + ) : ( + + + {() => ( + <> + {robotType === OT2_ROBOT_TYPE ? ( + + ) : ( + <> + {filteredAddressableAreas.map(addressableArea => { + const cutoutId = getCutoutIdForAddressableArea( + addressableArea.id, + deckDef.cutoutFixtures + ) + return cutoutId != null ? ( + + ) : null + })} + {stagingAreaFixtures.map(fixture => ( + + ))} + {trash != null + ? trashBinFixtures.map(({ cutoutId }) => + cutoutId != null ? ( + + + + + ) : null + ) + : null} + {wasteChuteFixtures.map(fixture => ( + + ))} + {wasteChuteStagingAreaFixtures.map(fixture => ( + + ))} + + )} + areas.location as CutoutId + )} + {...{ + deckDef, + showGen1MultichannelCollisionWarnings, + }} + /> + 0} + /> + {hover != null ? ( + + ) : null} + + )} + + + )} + + ) +} diff --git a/protocol-designer/src/pages/ProtocolOverview/DeckSetup/utils.ts b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/utils.ts new file mode 100644 index 00000000000..7e0e3457352 --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/DeckSetup/utils.ts @@ -0,0 +1,20 @@ +import type { + AddressableAreaName, + CutoutFixture, + CutoutId, +} from '@opentrons/shared-data' + +export function getCutoutIdForAddressableArea( + addressableArea: AddressableAreaName, + cutoutFixtures: CutoutFixture[] +): CutoutId | null { + return cutoutFixtures.reduce((acc, cutoutFixture) => { + const [cutoutId] = + Object.entries( + cutoutFixture.providesAddressableAreas + ).find(([_cutoutId, providedAAs]) => + providedAAs.includes(addressableArea) + ) ?? [] + return (cutoutId as CutoutId) ?? acc + }, null) +} diff --git a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx index d2b34d8ff5e..2a12f2b1df8 100644 --- a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx @@ -1,3 +1,26 @@ -import { it } from 'vitest' +import * as React from 'react' +import { describe, it, vi, beforeEach } from 'vitest' +import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../localization' +import { DeckSetup } from '../DeckSetup' +import { ProtocolOverview } from '../index' +import { fireEvent, screen } from '@testing-library/react' -it.todo('write test for ProtocolOverview') +vi.mock('../DeckSetup') + +const render = () => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('ProtocolOverview', () => { + beforeEach(() => { + vi.mocked(DeckSetup).mockReturnValue(
mock DeckSetup
) + }) + it('renders the deck setup component when the button is clicked', () => { + render() + fireEvent.click(screen.getByText('go to deck setup')) + screen.getByText('mock DeckSetup') + }) +}) diff --git a/protocol-designer/src/pages/ProtocolOverview/index.tsx b/protocol-designer/src/pages/ProtocolOverview/index.tsx index a6be423cc74..298a3b5e323 100644 --- a/protocol-designer/src/pages/ProtocolOverview/index.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/index.tsx @@ -1,8 +1,31 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' +import { Flex, PrimaryButton, SPACING } from '@opentrons/components' +import { DeckSetup } from './DeckSetup' export function ProtocolOverview(): JSX.Element { const { t } = useTranslation('protocol_overview') + const [deckSetup, setDeckSetup] = React.useState(false) - return
{t('protocol_overview')}
+ return deckSetup ? ( + { + setDeckSetup(false) + }} + onSave={() => { + setDeckSetup(false) + }} + /> + ) : ( + + {t('protocol_overview')} + { + setDeckSetup(true) + }} + > + go to deck setup + + + ) }