From 31422df112d01971c358d57226f40c04b796b165 Mon Sep 17 00:00:00 2001 From: Jethary Date: Tue, 17 Oct 2023 15:27:48 -0400 Subject: [PATCH 1/4] feat(protocol-designer): error handling in create file wizard closes RAUT-801, RAUT-802, RAUT-786 --- .../src/components/DeckSetup/index.tsx | 47 ++++---- .../CreateFileWizard/ModulesAndOtherTile.tsx | 75 +++++------- .../__tests__/CreateFileWizard.test.tsx | 2 +- .../__tests__/StagingAreaTile.test.tsx | 2 +- .../CreateFileWizard/__tests__/utils.test.tsx | 81 +++++++++++++ .../modals/CreateFileWizard/index.tsx | 26 ++-- .../modals/CreateFileWizard/utils.ts | 112 ++++++++++++++++++ .../__tests__/StagingAreaModal.test.tsx | 2 +- .../__tests__/StagingAreasRow.test.tsx | 2 +- .../src/localization/en/modal.json | 2 +- .../src/localization/en/modules.json | 2 +- 11 files changed, 267 insertions(+), 86 deletions(-) create mode 100644 protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx create mode 100644 protocol-designer/src/components/modals/CreateFileWizard/utils.ts diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx index ec9c759fff9..580d89c5460 100644 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ b/protocol-designer/src/components/DeckSetup/index.tsx @@ -36,6 +36,9 @@ import { RobotType, FLEX_ROBOT_TYPE, Cutout, + TRASH_BIN_LOAD_NAME, + STAGING_AREA_LOAD_NAME, + WASTE_CHUTE_LOAD_NAME, } from '@opentrons/shared-data' import { FLEX_TRASH_DEF_URI, @@ -107,7 +110,7 @@ export const DeckSetupContents = (props: ContentsProps): JSX.Element => { robotType, trashSlot, } = props - + console.log(deckSlotsById) // NOTE: handling module<>labware compat when moving labware to empty module // is handled by SlotControls. // But when swapping labware when at least one is on a module, we need to be aware @@ -494,15 +497,15 @@ export const DeckSetup = (): JSX.Element => { { fixtureId: trash?.id, fixtureLocation: trash?.slot as Cutout, - loadName: 'trashBin', + loadName: TRASH_BIN_LOAD_NAME, }, ] const wasteChuteFixtures = Object.values( activeDeckSetup.additionalEquipmentOnDeck - ).filter(aE => aE.name === 'wasteChute') + ).filter(aE => aE.name === WASTE_CHUTE_LOAD_NAME) const stagingAreaFixtures: AdditionalEquipmentEntity[] = Object.values( activeDeckSetup.additionalEquipmentOnDeck - ).filter(aE => aE.name === 'stagingArea') + ).filter(aE => aE.name === STAGING_AREA_LOAD_NAME) const locations = Object.values( activeDeckSetup.additionalEquipmentOnDeck ).map(aE => aE.location) @@ -545,22 +548,26 @@ export const DeckSetup = (): JSX.Element => { fixtureBaseColor={lightFill} /> ))} - {trashBinFixtures.map(fixture => ( - - - - - ))} + {trash != null + ? trashBinFixtures.map(fixture => ( + + + + + )) + : null} {wasteChuteFixtures.map(fixture => ( { - const formModule = modules[moduleType] - const crashableModuleOnDeck = - formModule?.onDeck && formModule?.model != null - ? isModuleWithCollisionIssue(formModule.model) - : false - - return crashableModuleOnDeck +export const DEFAULT_SLOT_MAP: { [moduleModel in ModuleModel]?: string } = { + [THERMOCYCLER_MODULE_V2]: 'B1', + [HEATERSHAKER_MODULE_V1]: 'D1', + [MAGNETIC_BLOCK_V1]: 'D2', + [TEMPERATURE_MODULE_V2]: 'C1', } +export const FLEX_SUPPORTED_MODULE_MODELS: ModuleModel[] = [ + THERMOCYCLER_MODULE_V2, + HEATERSHAKER_MODULE_V1, + MAGNETIC_BLOCK_V1, + TEMPERATURE_MODULE_V2, +] export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element { const { @@ -200,20 +197,6 @@ export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element { ) } - -const FLEX_SUPPORTED_MODULE_MODELS: ModuleModel[] = [ - THERMOCYCLER_MODULE_V2, - HEATERSHAKER_MODULE_V1, - MAGNETIC_BLOCK_V1, - TEMPERATURE_MODULE_V2, -] -const DEFAULT_SLOT_MAP: { [moduleModel in ModuleModel]?: string } = { - [THERMOCYCLER_MODULE_V2]: 'B1', - [HEATERSHAKER_MODULE_V1]: 'D1', - [MAGNETIC_BLOCK_V1]: 'D2', - [TEMPERATURE_MODULE_V2]: 'C1', -} - interface FlexModuleFieldsProps extends WizardTileProps { enableDeckModification: boolean } @@ -221,14 +204,7 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { const { values, setFieldValue, enableDeckModification } = props const isFlex = values.fields.robotType === FLEX_ROBOT_TYPE - const allStagingAreasInUse = - values.additionalEquipment.filter(equipment => - equipment.includes('stagingArea') - ).length === 4 - const allModulesInSideSlotsOnDeck = - values.modulesByType.heaterShakerModuleType.onDeck && - values.modulesByType.thermocyclerModuleType.onDeck && - values.modulesByType.temperatureModuleType.onDeck + const trashDisabled = getTrashBinOptionDisabled(values) const handleSetEquipmentOption = (equipment: AdditionalEquipment): void => { if (values.additionalEquipment.includes(equipment)) { @@ -244,17 +220,26 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { } } + React.useEffect(() => { + if (trashDisabled) { + setFieldValue( + 'additionalEquipment', + without(values.additionalEquipment, 'trashBin') + ) + } + }, [trashDisabled, setFieldValue]) + return ( {FLEX_SUPPORTED_MODULE_MODELS.map(moduleModel => { const moduleType = getModuleType(moduleModel) return ( } text={getModuleDisplayName(moduleModel)} + disabled={getLastCheckedEquipment(values) === moduleType} onClick={() => { if (values.modulesByType[moduleType].onDeck) { setFieldValue(`modulesByType.${moduleType}.onDeck`, false) @@ -311,7 +296,7 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { } text="Trash Bin" showCheckbox - disabled={allStagingAreasInUse && allModulesInSideSlotsOnDeck} + disabled={trashDisabled} /> ) : null} diff --git a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/CreateFileWizard.test.tsx b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/CreateFileWizard.test.tsx index e50c9f19af6..e7f1c117891 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/CreateFileWizard.test.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/CreateFileWizard.test.tsx @@ -209,7 +209,7 @@ describe('CreateFileWizard', () => { next.click() getByText('Step 6 / 7') // select a staging area - getByText('Staging areas') + getByText('Staging area slots') next = getByRole('button', { name: 'Next' }) next.click() getByText('Step 7 / 7') diff --git a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/StagingAreaTile.test.tsx b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/StagingAreaTile.test.tsx index 710fe4bfde8..87029c3450d 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/StagingAreaTile.test.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/StagingAreaTile.test.tsx @@ -54,7 +54,7 @@ describe('StagingAreaTile', () => { it('renders header and deck configurator', () => { props.values.fields.robotType = FLEX_ROBOT_TYPE const { getByText } = render(props) - getByText('Staging areas') + getByText('Staging area slots') getByText('mock deck configurator') }) }) diff --git a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx new file mode 100644 index 00000000000..e89aaf7f352 --- /dev/null +++ b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx @@ -0,0 +1,81 @@ +import { + FLEX_ROBOT_TYPE, + TEMPERATURE_MODULE_TYPE, +} from '@opentrons/shared-data' +import { + FLEX_TRASH_DEFAULT_SLOT, + getLastCheckedEquipment, + getTrashSlot, +} from '../utils' +import type { + FormModulesByType, + FormPipettesByMount, +} from '../../../../step-forms' +import type { FormState } from '../types' + +let MOCK_FORM_STATE = { + fields: { + name: 'mockName', + description: 'mockDescription', + organizationOrAuthor: 'mockOrganizationOrAuthor', + robotType: FLEX_ROBOT_TYPE, + }, + pipettesByMount: { + left: { pipetteName: 'mockPipetteName', tiprackDefURI: 'mocktip' }, + right: { pipetteName: null, tiprackDefURI: null }, + } as FormPipettesByMount, + modulesByType: { + heaterShakerModuleType: { onDeck: false, model: null, slot: 'D1' }, + magneticBlockType: { onDeck: false, model: null, slot: 'D2' }, + temperatureModuleType: { onDeck: false, model: null, slot: 'C1' }, + thermocyclerModuleType: { onDeck: false, model: null, slot: 'B1' }, + } as FormModulesByType, + additionalEquipment: [], +} as FormState + +describe('getLastCheckedEquipment', () => { + it('should return null when there is no trash bin', () => { + const result = getLastCheckedEquipment(MOCK_FORM_STATE) + expect(result).toBe(null) + }) + it('should return null if not all the modules are selected', () => { + MOCK_FORM_STATE = { + ...MOCK_FORM_STATE, + additionalEquipment: ['trashBin'], + modulesByType: { + ...MOCK_FORM_STATE.modulesByType, + temperatureModuleType: { onDeck: true, model: null, slot: 'C1' }, + }, + } + const result = getLastCheckedEquipment(MOCK_FORM_STATE) + expect(result).toBe(null) + }) + it('should return temperature module if other modules are selected', () => { + MOCK_FORM_STATE = { + ...MOCK_FORM_STATE, + additionalEquipment: ['trashBin'], + modulesByType: { + ...MOCK_FORM_STATE.modulesByType, + heaterShakerModuleType: { onDeck: true, model: null, slot: 'D1' }, + thermocyclerModuleType: { onDeck: true, model: null, slot: 'B1' }, + }, + } + const result = getLastCheckedEquipment(MOCK_FORM_STATE) + expect(result).toBe(TEMPERATURE_MODULE_TYPE) + }) +}) + +describe('getTrashSlot', () => { + it('should return the default slot A3 when there is no staging area in that slot', () => { + const result = getTrashSlot(MOCK_FORM_STATE) + expect(result).toBe(FLEX_TRASH_DEFAULT_SLOT) + }) + it('should return B3 when there is a staging area in slot A3', () => { + MOCK_FORM_STATE = { + ...MOCK_FORM_STATE, + additionalEquipment: ['stagingArea_A3'], + } + const result = getTrashSlot(MOCK_FORM_STATE) + expect(result).toBe('B3') + }) +}) diff --git a/protocol-designer/src/components/modals/CreateFileWizard/index.tsx b/protocol-designer/src/components/modals/CreateFileWizard/index.tsx index 68fed6cfee2..7557057cdc6 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/index.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/index.tsx @@ -66,6 +66,7 @@ import { OT_2_TRASH_DEF_URI, } from '@opentrons/step-generation' import type { FormState } from './types' +import { getTrashSlot } from './utils' type WizardStep = | 'robotType' @@ -201,28 +202,21 @@ export function CreateFileWizard(): JSX.Element | null { // add trash if ( - enableDeckModification && - values.additionalEquipment.includes('trashBin') + (enableDeckModification && + values.additionalEquipment.includes('trashBin')) || + !enableDeckModification ) { // defaulting trash to appropriate locations - dispatch( - labwareIngredActions.createContainer({ - labwareDefURI: FLEX_TRASH_DEF_URI, - slot: 'A3', - }) - ) - } - if ( - !enableDeckModification || - (enableDeckModification && values.fields.robotType === OT2_ROBOT_TYPE) - ) { dispatch( labwareIngredActions.createContainer({ labwareDefURI: values.fields.robotType === FLEX_ROBOT_TYPE ? FLEX_TRASH_DEF_URI : OT_2_TRASH_DEF_URI, - slot: values.fields.robotType === FLEX_ROBOT_TYPE ? 'A3' : '12', + slot: + values.fields.robotType === FLEX_ROBOT_TYPE + ? getTrashSlot(values) + : '12', }) ) } @@ -347,7 +341,9 @@ const initialFormState: FormState = { slot: SPAN7_8_10_11_SLOT, }, }, - additionalEquipment: [], + // defaulting to selecting trashBin already to avoid user having to + // click to add a trash bin/waste chute. Delete once we support returnTip() + additionalEquipment: ['trashBin'], } const pipetteValidationShape = Yup.object().shape({ diff --git a/protocol-designer/src/components/modals/CreateFileWizard/utils.ts b/protocol-designer/src/components/modals/CreateFileWizard/utils.ts new file mode 100644 index 00000000000..e489ccc0452 --- /dev/null +++ b/protocol-designer/src/components/modals/CreateFileWizard/utils.ts @@ -0,0 +1,112 @@ +import { + getModuleType, + HEATERSHAKER_MODULE_TYPE, + TEMPERATURE_MODULE_TYPE, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' +import { OUTER_SLOTS_FLEX } from '../../../modules' +import { isModuleWithCollisionIssue } from '../../modules' +import { + FLEX_SUPPORTED_MODULE_MODELS, + DEFAULT_SLOT_MAP, +} from './ModulesAndOtherTile' + +import type { ModuleType } from '@opentrons/shared-data' +import type { FormModulesByType } from '../../../step-forms' +import type { FormState } from './types' + +export const FLEX_TRASH_DEFAULT_SLOT = 'A3' + +export const getLastCheckedEquipment = (values: FormState): string | null => { + const hasTrash = values.additionalEquipment.includes('trashBin') + + if (!hasTrash) { + return null + } + + if ( + values.modulesByType.heaterShakerModuleType.onDeck && + values.modulesByType.thermocyclerModuleType.onDeck + ) { + return TEMPERATURE_MODULE_TYPE + } + + if ( + values.modulesByType.heaterShakerModuleType.onDeck && + values.modulesByType.temperatureModuleType.onDeck + ) { + return THERMOCYCLER_MODULE_TYPE + } + + if ( + values.modulesByType.thermocyclerModuleType.onDeck && + values.modulesByType.temperatureModuleType.onDeck + ) { + return HEATERSHAKER_MODULE_TYPE + } + + return null +} + +export const getCrashableModuleSelected = ( + modules: FormModulesByType, + moduleType: ModuleType +): boolean => { + const formModule = modules[moduleType] + const crashableModuleOnDeck = + formModule?.onDeck && formModule?.model != null + ? isModuleWithCollisionIssue(formModule.model) + : false + + return crashableModuleOnDeck +} + +export const getTrashBinOptionDisabled = (values: FormState): boolean => { + const allStagingAreasInUse = + values.additionalEquipment.filter(equipment => + equipment.includes('stagingArea') + ).length === 4 + + const allModulesInSideSlotsOnDeck = + values.modulesByType.heaterShakerModuleType.onDeck && + values.modulesByType.thermocyclerModuleType.onDeck && + values.modulesByType.temperatureModuleType.onDeck + + return allStagingAreasInUse && allModulesInSideSlotsOnDeck +} + +export const getTrashSlot = (values: FormState): string => { + const stagingAreaLocations = values.additionalEquipment + .filter(equipment => equipment.includes('stagingArea')) + .map(stagingArea => stagingArea.split('_')[1]) + + // return default trash slot A3 if staging area is not on slot + if (!stagingAreaLocations.includes(FLEX_TRASH_DEFAULT_SLOT)) { + return FLEX_TRASH_DEFAULT_SLOT + } + + const moduleSlots: string[] = [] + FLEX_SUPPORTED_MODULE_MODELS.map(model => { + const moduleType = getModuleType(model) + if (values.modulesByType[moduleType].onDeck) { + const slot = String(DEFAULT_SLOT_MAP[model]) + return moduleType === THERMOCYCLER_MODULE_TYPE + ? moduleSlots.push('A1', slot) + : moduleSlots.push(slot) + } + }) + + const unoccupiedSlot = OUTER_SLOTS_FLEX.find( + slot => + !stagingAreaLocations.includes(slot.value) && + !moduleSlots.includes(slot.value) + ) + if (unoccupiedSlot == null) { + console.error( + 'Expected to find an unoccupied slot for the trash bin but could not' + ) + return '' + } + + return unoccupiedSlot?.value +} diff --git a/protocol-designer/src/components/modules/__tests__/StagingAreaModal.test.tsx b/protocol-designer/src/components/modules/__tests__/StagingAreaModal.test.tsx index a55a675b78e..5ec3ce11063 100644 --- a/protocol-designer/src/components/modules/__tests__/StagingAreaModal.test.tsx +++ b/protocol-designer/src/components/modules/__tests__/StagingAreaModal.test.tsx @@ -44,7 +44,7 @@ describe('StagingAreasModal', () => { it('renders the deck, header, and buttons work as expected', () => { const { getByText, getByRole } = render(props) getByText('mock deck config') - getByText('Staging Areas') + getByText('Staging Area Slots') getByRole('button', { name: 'cancel' }).click() expect(props.onCloseClick).toHaveBeenCalled() }) diff --git a/protocol-designer/src/components/modules/__tests__/StagingAreasRow.test.tsx b/protocol-designer/src/components/modules/__tests__/StagingAreasRow.test.tsx index 08452c1f0c2..baa7ad21fbe 100644 --- a/protocol-designer/src/components/modules/__tests__/StagingAreasRow.test.tsx +++ b/protocol-designer/src/components/modules/__tests__/StagingAreasRow.test.tsx @@ -30,7 +30,7 @@ describe('StagingAreasRow', () => { }) it('renders no staging areas', () => { const { getByRole, getByText } = render(props) - getByText('Staging Areas') + getByText('Staging Area Slots') getByRole('button', { name: 'add' }).click() getByText('mock portal') }) diff --git a/protocol-designer/src/localization/en/modal.json b/protocol-designer/src/localization/en/modal.json index 3ba2299b7f7..db60f68231c 100644 --- a/protocol-designer/src/localization/en/modal.json +++ b/protocol-designer/src/localization/en/modal.json @@ -89,7 +89,7 @@ "robot_type": "Robot Type", "upload_tiprack": "Upload a custom tiprack to select its definition", "upload": "Upload", - "staging_areas": "Staging areas" + "staging_areas": "Staging area slots" }, "well_order": { "title": "Well Order", diff --git a/protocol-designer/src/localization/en/modules.json b/protocol-designer/src/localization/en/modules.json index ee74aca70c1..10a50dc0775 100644 --- a/protocol-designer/src/localization/en/modules.json +++ b/protocol-designer/src/localization/en/modules.json @@ -1,7 +1,7 @@ { "additional_equipment_display_names": { "gripper": "Flex Gripper", - "stagingAreas": "Staging Areas", + "stagingAreas": "Staging Area Slots", "trashBin": "Trash Bin", "wasteChute": "Waste Chute" }, From 095b600531cf802f18e7de855e64b074e877b28f Mon Sep 17 00:00:00 2001 From: Jethary Date: Tue, 17 Oct 2023 15:37:43 -0400 Subject: [PATCH 2/4] clean up import order --- protocol-designer/src/components/DeckSetup/index.tsx | 1 - .../src/components/modals/CreateFileWizard/index.tsx | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx index 580d89c5460..7e80df35cff 100644 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ b/protocol-designer/src/components/DeckSetup/index.tsx @@ -110,7 +110,6 @@ export const DeckSetupContents = (props: ContentsProps): JSX.Element => { robotType, trashSlot, } = props - console.log(deckSlotsById) // NOTE: handling module<>labware compat when moving labware to empty module // is handled by SlotControls. // But when swapping labware when at least one is on a module, we need to be aware diff --git a/protocol-designer/src/components/modals/CreateFileWizard/index.tsx b/protocol-designer/src/components/modals/CreateFileWizard/index.tsx index 7557057cdc6..f7e6c78acd9 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/index.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/index.tsx @@ -8,6 +8,7 @@ import uniq from 'lodash/uniq' import { Formik, FormikProps } from 'formik' import * as Yup from 'yup' import { ModalShell } from '@opentrons/components' +import { OT_2_TRASH_DEF_URI } from '@opentrons/step-generation' import { ModuleType, ModuleModel, @@ -60,13 +61,10 @@ import { FirstPipetteTipsTile, SecondPipetteTipsTile } from './PipetteTipsTile' import { ModulesAndOtherTile } from './ModulesAndOtherTile' import { WizardHeader } from './WizardHeader' import { StagingAreaTile } from './StagingAreaTile' +import { getTrashSlot } from './utils' -import { - NormalizedPipette, - OT_2_TRASH_DEF_URI, -} from '@opentrons/step-generation' +import type { NormalizedPipette } from '@opentrons/step-generation' import type { FormState } from './types' -import { getTrashSlot } from './utils' type WizardStep = | 'robotType' From 8f317aa6abc9b05b75f7aaf702317da104554afc Mon Sep 17 00:00:00 2001 From: Jethary Date: Wed, 18 Oct 2023 17:16:57 -0400 Subject: [PATCH 3/4] address comments, extend logic for staging area slots --- .../CreateFileWizard/ModulesAndOtherTile.tsx | 8 ++--- .../modals/CreateFileWizard/utils.ts | 34 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/protocol-designer/src/components/modals/CreateFileWizard/ModulesAndOtherTile.tsx b/protocol-designer/src/components/modals/CreateFileWizard/ModulesAndOtherTile.tsx index 8c037f98f90..339813e2bc0 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/ModulesAndOtherTile.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/ModulesAndOtherTile.tsx @@ -204,7 +204,7 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { const { values, setFieldValue, enableDeckModification } = props const isFlex = values.fields.robotType === FLEX_ROBOT_TYPE - const trashDisabled = getTrashBinOptionDisabled(values) + const trashBinDisabled = getTrashBinOptionDisabled(values) const handleSetEquipmentOption = (equipment: AdditionalEquipment): void => { if (values.additionalEquipment.includes(equipment)) { @@ -221,13 +221,13 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { } React.useEffect(() => { - if (trashDisabled) { + if (trashBinDisabled) { setFieldValue( 'additionalEquipment', without(values.additionalEquipment, 'trashBin') ) } - }, [trashDisabled, setFieldValue]) + }, [trashBinDisabled, setFieldValue]) return ( @@ -296,7 +296,7 @@ function FlexModuleFields(props: FlexModuleFieldsProps): JSX.Element { } text="Trash Bin" showCheckbox - disabled={trashDisabled} + disabled={trashBinDisabled} /> ) : null} diff --git a/protocol-designer/src/components/modals/CreateFileWizard/utils.ts b/protocol-designer/src/components/modals/CreateFileWizard/utils.ts index e489ccc0452..84e5b585c99 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/utils.ts +++ b/protocol-designer/src/components/modals/CreateFileWizard/utils.ts @@ -16,11 +16,16 @@ import type { FormModulesByType } from '../../../step-forms' import type { FormState } from './types' export const FLEX_TRASH_DEFAULT_SLOT = 'A3' +const ALL_STAGING_AREAS = 4 export const getLastCheckedEquipment = (values: FormState): string | null => { - const hasTrash = values.additionalEquipment.includes('trashBin') + const hasAllStagingAreas = + values.additionalEquipment.filter(equipment => + equipment.includes('stagingArea') + ).length === ALL_STAGING_AREAS + const hasTrashBin = values.additionalEquipment.includes('trashBin') - if (!hasTrash) { + if (!hasTrashBin || !hasAllStagingAreas) { return null } @@ -65,7 +70,7 @@ export const getTrashBinOptionDisabled = (values: FormState): boolean => { const allStagingAreasInUse = values.additionalEquipment.filter(equipment => equipment.includes('stagingArea') - ).length === 4 + ).length === ALL_STAGING_AREAS const allModulesInSideSlotsOnDeck = values.modulesByType.heaterShakerModuleType.onDeck && @@ -85,16 +90,19 @@ export const getTrashSlot = (values: FormState): string => { return FLEX_TRASH_DEFAULT_SLOT } - const moduleSlots: string[] = [] - FLEX_SUPPORTED_MODULE_MODELS.map(model => { - const moduleType = getModuleType(model) - if (values.modulesByType[moduleType].onDeck) { - const slot = String(DEFAULT_SLOT_MAP[model]) - return moduleType === THERMOCYCLER_MODULE_TYPE - ? moduleSlots.push('A1', slot) - : moduleSlots.push(slot) - } - }) + const moduleSlots: string[] = FLEX_SUPPORTED_MODULE_MODELS.reduce( + (slots: string[], model) => { + const moduleType = getModuleType(model) + if (values.modulesByType[moduleType].onDeck) { + const slot = String(DEFAULT_SLOT_MAP[model]) + return moduleType === THERMOCYCLER_MODULE_TYPE + ? [...slots, 'A1', slot] + : [...slots, slot] + } + return slots + }, + [] + ) const unoccupiedSlot = OUTER_SLOTS_FLEX.find( slot => From 739000709c645f212a38e42c4214f6339d4e1af3 Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 19 Oct 2023 08:05:02 -0400 Subject: [PATCH 4/4] fix test --- .../CreateFileWizard/__tests__/utils.test.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx index e89aaf7f352..fff9f9c446b 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/__tests__/utils.test.tsx @@ -38,7 +38,7 @@ describe('getLastCheckedEquipment', () => { const result = getLastCheckedEquipment(MOCK_FORM_STATE) expect(result).toBe(null) }) - it('should return null if not all the modules are selected', () => { + it('should return null if not all the modules or staging areas are selected', () => { MOCK_FORM_STATE = { ...MOCK_FORM_STATE, additionalEquipment: ['trashBin'], @@ -50,10 +50,16 @@ describe('getLastCheckedEquipment', () => { const result = getLastCheckedEquipment(MOCK_FORM_STATE) expect(result).toBe(null) }) - it('should return temperature module if other modules are selected', () => { + it('should return temperature module if other modules and staging areas are selected', () => { MOCK_FORM_STATE = { ...MOCK_FORM_STATE, - additionalEquipment: ['trashBin'], + additionalEquipment: [ + 'trashBin', + 'stagingArea_A3', + 'stagingArea_B3', + 'stagingArea_C3', + 'stagingArea_D3', + ], modulesByType: { ...MOCK_FORM_STATE.modulesByType, heaterShakerModuleType: { onDeck: true, model: null, slot: 'D1' }, @@ -67,6 +73,10 @@ describe('getLastCheckedEquipment', () => { describe('getTrashSlot', () => { it('should return the default slot A3 when there is no staging area in that slot', () => { + MOCK_FORM_STATE = { + ...MOCK_FORM_STATE, + additionalEquipment: ['trashBin'], + } const result = getTrashSlot(MOCK_FORM_STATE) expect(result).toBe(FLEX_TRASH_DEFAULT_SLOT) })