diff --git a/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx b/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx index 4073d7838af..b606eee186c 100644 --- a/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx +++ b/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx @@ -1,32 +1,25 @@ import * as React from 'react' import map from 'lodash/map' import { useTranslation } from 'react-i18next' -import { - BaseDeck, - EXTENDED_DECK_CONFIG_FIXTURE, - LabwareRender, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getDeckDefFromRobotType, - LabwareDefinition2, - THERMOCYCLER_MODULE_V1, -} from '@opentrons/shared-data' -import { RunTimeCommand } from '@opentrons/shared-data' +import { BaseDeck, EXTENDED_DECK_CONFIG_FIXTURE } from '@opentrons/components' +import { FLEX_ROBOT_TYPE, THERMOCYCLER_MODULE_V1 } from '@opentrons/shared-data' import { Modal } from '../../molecules/Modal' import { getDeckConfigFromProtocolCommands } from '../../resources/deck_configuration/utils' import { useFeatureFlag } from '../../redux/config' import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' import { getLabwareRenderInfo } from '../Devices/ProtocolRun/utils/getLabwareRenderInfo' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' +import type { + CompletedProtocolAnalysis, + DeckDefinition, + LabwareDefinition2, +} from '@opentrons/shared-data' import type { LoadedLabwareByAdapter } from '@opentrons/api-client' import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' interface LabwareMapViewModalProps { - runId: string attachedProtocolModuleMatches: AttachedProtocolModuleMatch[] handleLabwareClick: ( labwareDef: LabwareDefinition2, @@ -34,26 +27,26 @@ interface LabwareMapViewModalProps { ) => void onCloseClick: () => void initialLoadedLabwareByAdapter: LoadedLabwareByAdapter - commands: RunTimeCommand[] + deckDef: DeckDefinition + mostRecentAnalysis: CompletedProtocolAnalysis | null } -export function LabwareMapViewModal({ - handleLabwareClick, - runId, - onCloseClick, - attachedProtocolModuleMatches, - initialLoadedLabwareByAdapter, - commands, -}: LabwareMapViewModalProps): JSX.Element { +export function LabwareMapViewModal( + props: LabwareMapViewModalProps +): JSX.Element { + const { + handleLabwareClick, + onCloseClick, + attachedProtocolModuleMatches, + initialLoadedLabwareByAdapter, + deckDef, + mostRecentAnalysis, + } = props const { t } = useTranslation('protocol_setup') const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - const deckConfig = enableDeckConfig ? EXTENDED_DECK_CONFIG_FIXTURE - : getDeckConfigFromProtocolCommands(commands) - - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + : getDeckConfigFromProtocolCommands(mostRecentAnalysis?.commands ?? []) const labwareRenderInfo = mostRecentAnalysis != null ? getLabwareRenderInfo(mostRecentAnalysis, deckDef) @@ -65,14 +58,7 @@ export function LabwareMapViewModal({ } const moduleLocations = attachedProtocolModuleMatches.map(module => { - const { - moduleDef, - nestedLabwareDef, - nestedLabwareId, - slotName, - x, - y, - } = module + const { moduleDef, nestedLabwareDef, nestedLabwareId, slotName } = module const labwareInAdapterInMod = nestedLabwareId != null ? initialLoadedLabwareByAdapter[nestedLabwareId] @@ -83,6 +69,7 @@ export function LabwareMapViewModal({ labwareInAdapterInMod?.result?.definition ?? nestedLabwareDef const topLabwareId = labwareInAdapterInMod?.result?.labwareId ?? nestedLabwareId + return { moduleModel: moduleDef.model, moduleLocation: { slotName }, @@ -91,25 +78,17 @@ export function LabwareMapViewModal({ ? { lidMotorState: 'open' } : {}, nestedLabwareDef: topLabwareDefinition, - moduleChildren: - topLabwareDefinition != null && topLabwareId != null ? ( - - - handleLabwareClick(topLabwareDefinition, topLabwareId) - } - /> - - ) : null, + onLabwareClick: + topLabwareDefinition != null && topLabwareId != null + ? () => handleLabwareClick(topLabwareDefinition, topLabwareId) + : undefined, + moduleChildren: null, } }) const labwareLocations = map( labwareRenderInfo, - ({ x, y, labwareDef, slotName }, labwareId) => { + ({ labwareDef, slotName }, labwareId) => { const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId] // only rendering the labware on top most layer so // either the adapter or the labware are rendered but not both @@ -121,16 +100,9 @@ export function LabwareMapViewModal({ labwareLocation: { slotName }, definition: topLabwareDefinition, topLabwareId, - labwareChildren: ( - - - handleLabwareClick(topLabwareDefinition, topLabwareId) - } - /> - - ), + onLabwareClick: () => + handleLabwareClick(topLabwareDefinition, topLabwareId), + labwareChildren: null, } } ) diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx b/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx new file mode 100644 index 00000000000..391ec650de9 --- /dev/null +++ b/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx @@ -0,0 +1,145 @@ +import * as React from 'react' +import { StaticRouter } from 'react-router-dom' +import { when, resetAllWhenMocks } from 'jest-when' +import { + renderWithProviders, + BaseDeck, + EXTENDED_DECK_CONFIG_FIXTURE, +} from '@opentrons/components' +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' +import deckDefFixture from '@opentrons/shared-data/deck/fixtures/3/deckExample.json' +import fixture_tiprack_300_ul from '@opentrons/shared-data/labware/fixtures/2/fixture_tiprack_300_ul.json' +import { i18n } from '../../../i18n' +import { getDeckConfigFromProtocolCommands } from '../../../resources/deck_configuration/utils' +import { useFeatureFlag } from '../../../redux/config' +import { getLabwareRenderInfo } from '../../Devices/ProtocolRun/utils/getLabwareRenderInfo' +import { getStandardDeckViewLayerBlockList } from '../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' +import { mockProtocolModuleInfo } from '../__fixtures__' +import { LabwareMapViewModal } from '../LabwareMapViewModal' + +import type { + CompletedProtocolAnalysis, + DeckDefinition, + LabwareDefinition2, + ModuleModel, +} from '@opentrons/shared-data' + +jest.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') +jest.mock('@opentrons/components/src/hardware-sim/Labware/LabwareRender') +jest.mock('@opentrons/components/src/hardware-sim/BaseDeck') +jest.mock('../../../resources/deck_configuration/utils') +jest.mock('../../../redux/config') + +const mockGetLabwareRenderInfo = getLabwareRenderInfo as jest.MockedFunction< + typeof getLabwareRenderInfo +> +const mockGetDeckConfigFromProtocolCommands = getDeckConfigFromProtocolCommands as jest.MockedFunction< + typeof getDeckConfigFromProtocolCommands +> +const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< + typeof useFeatureFlag +> + +const mockBaseDeck = BaseDeck as jest.MockedFunction +const MOCK_300_UL_TIPRACK_COORDS = [30, 40, 0] + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} + +describe('LabwareMapViewModal', () => { + beforeEach(() => { + mockGetLabwareRenderInfo.mockReturnValue({}) + mockGetDeckConfigFromProtocolCommands.mockReturnValue([]) + when(mockUseFeatureFlag) + .calledWith('enableDeckConfiguration') + .mockReturnValue(true) + }) + + afterEach(() => { + resetAllWhenMocks() + }) + it('should render nothing on the deck and calls exit button', () => { + mockBaseDeck.mockReturnValue(
mock base deck
) + + const props = { + handleLabwareClick: jest.fn(), + onCloseClick: jest.fn(), + deckDef: (deckDefFixture as unknown) as DeckDefinition, + mostRecentAnalysis: ({ + commands: [], + labware: [], + } as unknown) as CompletedProtocolAnalysis, + initialLoadedLabwareByAdapter: {}, + attachedProtocolModuleMatches: [], + } + + const { getByText, getByLabelText } = render(props) + getByText('Map View') + getByText('mock base deck') + getByLabelText('closeIcon').click() + expect(props.onCloseClick).toHaveBeenCalled() + }) + + it('should render a deck with modules and labware', () => { + const mockLabwareLocations = [ + { + labwareLocation: { slotName: 'C1' }, + definition: fixture_tiprack_300_ul as LabwareDefinition2, + topLabwareId: '300_ul_tiprack_id', + onLabwareClick: expect.any(Function), + labwareChildren: null, + }, + ] + const mockModuleLocations = [ + { + moduleModel: 'heaterShakerModuleV1' as ModuleModel, + moduleLocation: { slotName: 'B1' }, + nestedLabwareDef: mockProtocolModuleInfo[0] + .nestedLabwareDef as LabwareDefinition2, + onLabwareClick: expect.any(Function), + moduleChildren: null, + innerProps: {}, + }, + ] + when(mockBaseDeck) + .calledWith({ + robotType: FLEX_ROBOT_TYPE, + deckLayerBlocklist: getStandardDeckViewLayerBlockList(FLEX_ROBOT_TYPE), + deckConfig: EXTENDED_DECK_CONFIG_FIXTURE, + labwareLocations: mockLabwareLocations, + moduleLocations: mockModuleLocations, + }) + .mockReturnValue(
mock base deck
) + mockGetLabwareRenderInfo.mockReturnValue({ + '300_ul_tiprack_id': { + labwareDef: fixture_tiprack_300_ul as LabwareDefinition2, + displayName: 'fresh tips', + x: MOCK_300_UL_TIPRACK_COORDS[0], + y: MOCK_300_UL_TIPRACK_COORDS[1], + z: MOCK_300_UL_TIPRACK_COORDS[2], + slotName: 'C1', + }, + }) + render({ + handleLabwareClick: jest.fn(), + onCloseClick: jest.fn(), + deckDef: (deckDefFixture as unknown) as DeckDefinition, + mostRecentAnalysis: ({} as unknown) as CompletedProtocolAnalysis, + initialLoadedLabwareByAdapter: {}, + attachedProtocolModuleMatches: [ + { + ...mockProtocolModuleInfo[0], + }, + ], + }) + expect(mockBaseDeck).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ProtocolSetupLabware/index.tsx b/app/src/organisms/ProtocolSetupLabware/index.tsx index 683cdb85dcf..65da8173326 100644 --- a/app/src/organisms/ProtocolSetupLabware/index.tsx +++ b/app/src/organisms/ProtocolSetupLabware/index.tsx @@ -206,8 +206,8 @@ export function ProtocolSetupLabware({ {showDeckMapModal ? ( setShowDeckMapModal(false)} diff --git a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx index a55b4c66245..a29f33028b3 100644 --- a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx +++ b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx @@ -43,6 +43,7 @@ interface BaseDeckProps { definition: LabwareDefinition2 // generic prop to render self-positioned children for each labware labwareChildren?: React.ReactNode + onLabwareClick?: () => void }> moduleLocations: Array<{ moduleModel: ModuleModel @@ -51,6 +52,7 @@ interface BaseDeckProps { innerProps?: React.ComponentProps['innerProps'] // generic prop to render self-positioned children for each module moduleChildren?: React.ReactNode + onLabwareClick?: () => void }> deckConfig?: DeckConfiguration deckLayerBlocklist?: string[] @@ -150,6 +152,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { nestedLabwareDef, innerProps, moduleChildren, + onLabwareClick, }) => { const slotDef = deckDef.locations.orderedSlots.find( s => s.id === moduleLocation.slotName @@ -167,7 +170,10 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { innerProps={innerProps} > {nestedLabwareDef != null ? ( - + ) : null} {moduleChildren} @@ -175,7 +181,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { } )} {labwareLocations.map( - ({ labwareLocation, definition, labwareChildren }) => { + ({ labwareLocation, definition, labwareChildren, onLabwareClick }) => { const slotDef = deckDef.locations.orderedSlots.find( s => labwareLocation !== 'offDeck' && @@ -187,7 +193,10 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { key={slotDef.id} transform={`translate(${slotDef.position[0]},${slotDef.position[1]})`} > - + {labwareChildren} ) : null diff --git a/components/src/hardware-sim/Labware/LabwareRender.tsx b/components/src/hardware-sim/Labware/LabwareRender.tsx index ba515c73b94..12f67ac48b7 100644 --- a/components/src/hardware-sim/Labware/LabwareRender.tsx +++ b/components/src/hardware-sim/Labware/LabwareRender.tsx @@ -63,7 +63,7 @@ export interface LabwareRenderProps { export const LabwareRender = (props: LabwareRenderProps): JSX.Element => { const { gRef } = props const cornerOffsetFromSlot = props.definition.cornerOffsetFromSlot - + return (