Skip to content

Commit 3c40e0d

Browse files
fix(app): Do not show trash bin if there is a stacker attached next to it (#19007)
1 parent ec31e87 commit 3c40e0d

File tree

7 files changed

+136
-33
lines changed

7 files changed

+136
-33
lines changed

app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react'
1+
import { useEffect, useState } from 'react'
22
import { useTranslation } from 'react-i18next'
33

44
import {
@@ -27,7 +27,11 @@ import { ODDFixtureOption } from '/app/molecules/ODDFixtureOption'
2727
import { OddModal } from '/app/molecules/OddModal'
2828
import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration/'
2929

30-
import { getOptions } from '../DeviceDetailsDeckConfiguration/utils'
30+
import {
31+
getFixtureOptions,
32+
getOptions,
33+
getWasteChuteOptions,
34+
} from '../DeviceDetailsDeckConfiguration/utils'
3135
import { useSendIdentifyStacker } from '../ModuleWizardFlows/hooks'
3236

3337
import type { AttachedModule } from '@opentrons/api-client'
@@ -36,6 +40,7 @@ import type {
3640
AddressableAreaNamesWithFakes,
3741
CutoutConfig,
3842
CutoutConfigMap,
43+
CutoutFixtureId,
3944
CutoutId,
4045
DeckDefinition,
4146
} from '@opentrons/shared-data'
@@ -50,6 +55,7 @@ interface AddFixtureModalProps {
5055
closeModal: () => void
5156
deckDef: DeckDefinition
5257
isOnDevice?: boolean
58+
existingCutoutFixtureId?: CutoutFixtureId
5359
}
5460
type OptionStage =
5561
| 'modulesOrFixtures'
@@ -64,6 +70,7 @@ export function AddFixtureModal({
6470
closeModal,
6571
isOnDevice = false,
6672
deckDef,
73+
existingCutoutFixtureId,
6774
}: AddFixtureModalProps): JSX.Element {
6875
const { t } = useTranslation(['device_details', 'shared'])
6976
const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation()
@@ -87,6 +94,23 @@ export function AddFixtureModal({
8794
: 'modulesOrFixtures'
8895
const [optionStage, setOptionStage] = useState<OptionStage>(initialStage)
8996

97+
// Bind allFixtureOptions with useEffect
98+
const [allFixtureOptions, setAllFixtureOptions] = useState<
99+
CutoutConfigMap[][]
100+
>([])
101+
102+
useEffect(() => {
103+
const options = [
104+
...getFixtureOptions(
105+
cutoutId,
106+
addressableAreaId,
107+
existingCutoutFixtureId
108+
),
109+
...getWasteChuteOptions(cutoutId),
110+
]
111+
setAllFixtureOptions(options)
112+
}, [cutoutId, addressableAreaId, existingCutoutFixtureId])
113+
90114
const modalHeader: OddModalHeaderBaseProps = {
91115
title: t('add_to', {
92116
slotName: getAADisplayName(addressableAreaId),
@@ -110,7 +134,8 @@ export function AddFixtureModal({
110134
unconfiguredMods,
111135
optionStage,
112136
addressableAreaId,
113-
deckDef
137+
deckDef,
138+
existingCutoutFixtureId
114139
)
115140

116141
let nextStageOptions = null
@@ -138,11 +163,12 @@ export function AddFixtureModal({
138163
</>
139164
) : (
140165
<>
141-
{SINGLE_CENTER_CUTOUTS.includes(cutoutId) ? null : (
166+
{SINGLE_CENTER_CUTOUTS.includes(cutoutId) ||
167+
allFixtureOptions.length === 0 ? null : (
142168
<FixtureOption
143169
key="fixturesOption"
144170
optionName="Fixtures"
145-
buttonText={t('add')}
171+
buttonText={t('select_options')}
146172
onClickHandler={() => {
147173
setOptionStage('fixtureOptions')
148174
}}
@@ -151,7 +177,7 @@ export function AddFixtureModal({
151177
<FixtureOption
152178
key="modulesOption"
153179
optionName="Modules"
154-
buttonText={t('add')}
180+
buttonText={t('select_options')}
155181
onClickHandler={() => {
156182
setOptionStage('moduleOptions')
157183
}}

app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
useUpdateDeckConfigurationMutation,
77
} from '@opentrons/react-api-client'
88
import {
9+
FLEX_STACKER_V1_FIXTURE,
910
getDeckDefFromRobotType,
1011
getFixtureDisplayName,
1112
WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE,
@@ -31,6 +32,10 @@ vi.mock('/app/organisms/ModuleWizardFlows/hooks.tsx')
3132
const mockCloseModal = vi.fn()
3233
const mockUpdateDeckConfiguration = vi.fn()
3334
const deckDef = getDeckDefFromRobotType('OT-3 Standard')
35+
const mockFixture = {
36+
cutoutId: 'cutoutD3',
37+
cutoutFixtureId: FLEX_STACKER_V1_FIXTURE,
38+
}
3439

3540
const render = (props: ComponentProps<typeof AddFixtureModal>) => {
3641
return renderWithProviders(<AddFixtureModal {...props} />, {
@@ -58,7 +63,7 @@ describe('Touchscreen AddFixtureModal', () => {
5863
updateDeckConfiguration: mockUpdateDeckConfiguration,
5964
} as any)
6065
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({
61-
data: [],
66+
data: [mockFixture],
6267
} as unknown) as UseQueryResult<DeckConfiguration>)
6368
vi.mocked(useModulesQuery).mockReturnValue(({
6469
data: { data: [] },
@@ -74,12 +79,12 @@ describe('Touchscreen AddFixtureModal', () => {
7479
)
7580
screen.getByText('Fixtures')
7681
screen.getByText('Modules')
77-
expect(screen.getAllByText('Add').length).toBe(2)
82+
expect(screen.getAllByText('Select options').length).toBe(2)
7883
})
7984

8085
it('should set deck config when tapping add button', () => {
8186
render(props)
82-
fireEvent.click(screen.getAllByText('Add')[1])
87+
fireEvent.click(screen.getAllByText('Select options')[1])
8388
fireEvent.click(screen.getAllByText('Add')[0])
8489
})
8590
})
@@ -97,6 +102,9 @@ describe('Desktop AddFixtureModal', () => {
97102
vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({
98103
updateDeckConfiguration: mockUpdateDeckConfiguration,
99104
} as any)
105+
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({
106+
data: [],
107+
} as unknown) as UseQueryResult<DeckConfiguration>)
100108
})
101109

102110
afterEach(() => {
@@ -112,14 +120,36 @@ describe('Desktop AddFixtureModal', () => {
112120

113121
screen.getByText('Fixtures')
114122
screen.getByText('Modules')
115-
fireEvent.click(screen.getAllByText('Add')[0])
123+
fireEvent.click(screen.getAllByText('Select options')[0])
116124
screen.getByText('Trash bin')
117125
expect(screen.getAllByRole('button', { name: 'Add' }).length).toBe(1)
118126
expect(
119127
screen.getAllByRole('button', { name: 'Select options' }).length
120128
).toBe(1)
121129
})
122130

131+
it('should not render trash bin text and buttons slot D3 with a stacker in the slot', () => {
132+
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({
133+
data: [mockFixture],
134+
} as unknown) as UseQueryResult<DeckConfiguration>)
135+
props = { ...props, existingCutoutFixtureId: FLEX_STACKER_V1_FIXTURE }
136+
render(props)
137+
screen.getByText('Add to Slot D3')
138+
screen.getByText(
139+
'Choose an item below to add to your deck configuration. It will be referenced during protocol analysis.'
140+
)
141+
142+
screen.getByText('Fixtures')
143+
screen.getByText('Modules')
144+
fireEvent.click(screen.getAllByText('Select options')[0])
145+
screen.getByText('Waste chute')
146+
// Verify trash bin is not rendered
147+
expect(screen.queryByText('Trash bin')).not.toBeInTheDocument()
148+
expect(
149+
screen.getAllByRole('button', { name: 'Select options' }).length
150+
).toBe(1)
151+
})
152+
123153
it('should render text and buttons slot A1', () => {
124154
props = { ...props, cutoutId: 'cutoutA1', addressableAreaId: 'A1' }
125155
render(props)
@@ -129,7 +159,7 @@ describe('Desktop AddFixtureModal', () => {
129159
)
130160
screen.getByText('Fixtures')
131161
screen.getByText('Modules')
132-
fireEvent.click(screen.getAllByText('Add')[0])
162+
fireEvent.click(screen.getAllByText('Select options')[0])
133163
screen.getByText('Trash bin')
134164
screen.getByRole('button', { name: 'Add' })
135165
})
@@ -143,9 +173,8 @@ describe('Desktop AddFixtureModal', () => {
143173
)
144174
screen.getByText('Fixtures')
145175
screen.getByText('Modules')
146-
fireEvent.click(screen.getAllByText('Add')[0])
176+
fireEvent.click(screen.getAllByText('Select options')[0])
147177
screen.getByText('Trash bin')
148-
console.log('screen: ', screen)
149178
expect(screen.getAllByRole('button', { name: 'Add' }).length).toBe(1)
150179
})
151180

@@ -163,14 +192,16 @@ describe('Desktop AddFixtureModal', () => {
163192
it('should call update deck config when add button is clicked', () => {
164193
props = { ...props, cutoutId: 'cutoutA1' }
165194
render(props)
166-
fireEvent.click(screen.getAllByText('Add')[0])
195+
fireEvent.click(screen.getAllByText('Select options')[0])
167196
fireEvent.click(screen.getByText('Add'))
168197
expect(mockUpdateDeckConfiguration).toHaveBeenCalled()
169198
})
170199

171200
it('should display appropriate Waste Chute options when the generic Waste Chute button is clicked', () => {
172201
render(props)
173-
fireEvent.click(screen.getAllByRole('button', { name: 'Add' })[0]) // click fixtures
202+
fireEvent.click(
203+
screen.getAllByRole('button', { name: 'Select options' })[0]
204+
) // click fixtures
174205
expect(screen.getAllByRole('button', { name: 'Add' }).length).toBe(1)
175206
expect(
176207
screen.getAllByRole('button', { name: 'Select options' }).length
@@ -184,8 +215,12 @@ describe('Desktop AddFixtureModal', () => {
184215

185216
it('should allow a user to exit the Waste Chute submenu by clicking "go back"', () => {
186217
render(props)
187-
expect(screen.getAllByRole('button', { name: 'Add' }).length).toBe(2)
188-
fireEvent.click(screen.getAllByRole('button', { name: 'Add' })[0]) // click fixtures
218+
expect(
219+
screen.getAllByRole('button', { name: 'Select options' }).length
220+
).toBe(2)
221+
fireEvent.click(
222+
screen.getAllByRole('button', { name: 'Select options' })[0]
223+
) // click fixtures
189224
screen.getByText('Waste chute')
190225
})
191226
})

app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/utils.test.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
22

33
import {
44
DEFAULT_AA_FOR_WASTE_CHUTE,
5+
FAKE_STAGING_AREA_RIGHT_SLOT,
56
FLEX_STACKER_MODULE_TYPE,
67
FLEX_STACKER_MODULE_V1,
78
getDeckDefFromRobotType,
@@ -211,8 +212,16 @@ describe('getFixtureOptions', () => {
211212
],
212213
])
213214
})
215+
it('Should not get a trash bin for cutoutD3 and aa D3 with a stacker in the slot', () => {
216+
const result = getFixtureOptions('cutoutD3', 'D3', FLEX_STACKER_MODULE_V1)
217+
expect(result).toEqual([])
218+
})
214219
it('Should get staging area for cutoutD3 and aa fakeD4', () => {
215-
const result = getFixtureOptions('cutoutD3', 'fakeD4')
220+
const result = getFixtureOptions(
221+
'cutoutD3',
222+
'fakeD4',
223+
FAKE_STAGING_AREA_RIGHT_SLOT
224+
)
216225
expect(result).toEqual([
217226
[
218227
{

app/src/organisms/DeviceDetailsDeckConfiguration/utils.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
AddressableAreaNamesWithFakes,
2020
CutoutConfigMap,
2121
CutoutFixtureId,
22+
CutoutFixtureIdsWithFakes,
2223
CutoutId,
2324
CutoutIdToCutoutFixtureId,
2425
DeckDefinition,
@@ -220,14 +221,17 @@ export const getWasteChuteOptions = (
220221

221222
export const getFixtureOptions = (
222223
cutoutId: CutoutId,
223-
addressableAreaId: AddressableAreaNamesWithFakes
224+
addressableAreaId: AddressableAreaNamesWithFakes,
225+
existingCutoutFixtureId?: CutoutFixtureIdsWithFakes
224226
): CutoutConfigMap[][] => {
225227
let availableOptions: CutoutConfigMap[][] = []
226228
const TrashBinAA = getMainAAForAFixture(
227229
cutoutId,
228230
TRASH_BIN_ADAPTER_FIXTURE,
229-
addressableAreaId
231+
addressableAreaId,
232+
existingCutoutFixtureId
230233
)
234+
231235
if (TrashBinAA != null) {
232236
availableOptions = [
233237
...availableOptions,
@@ -268,10 +272,15 @@ export const getOptions = (
268272
unconfiguredMods: AttachedModule[],
269273
optionStage: string,
270274
addressableAreaId: AddressableAreaNamesWithFakes,
271-
deckDefinition: DeckDefinition
275+
deckDefinition: DeckDefinition,
276+
existingCutoutFixtureId?: CutoutFixtureIdsWithFakes
272277
): CutoutConfigMap[][] => {
273278
if (optionStage === 'fixtureOptions') {
274-
return getFixtureOptions(cutoutId, addressableAreaId)
279+
return getFixtureOptions(
280+
cutoutId,
281+
addressableAreaId,
282+
existingCutoutFixtureId
283+
)
275284
}
276285
if (optionStage === 'moduleOptions') {
277286
return getModuleOptions(

app/src/resources/deck_configuration/hooks/useDeckConfigurationEditingTools.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useNotifyDeckConfigurationQuery } from '../useNotifyDeckConfigurationQu
1717
import type { ReactNode } from 'react'
1818
import type {
1919
AddressableAreaNamesWithFakes,
20+
CutoutFixtureId,
2021
CutoutFixtureIdsWithFakes,
2122
CutoutId,
2223
} from '@opentrons/shared-data'
@@ -50,12 +51,21 @@ export function useDeckConfigurationEditingTools(
5051
setAddressableAreaId,
5152
] = useState<AddressableAreaNamesWithFakes | null>(null)
5253

54+
const [
55+
existingCutoutFixtureId,
56+
setExistingCutoutFixtureId,
57+
] = useState<CutoutFixtureId | null>(null)
58+
5359
const addFixtureToCutout = (
5460
cutoutId: CutoutId,
5561
addressableAreaId: AddressableAreaNamesWithFakes
5662
): void => {
5763
setTargetCutoutId(cutoutId)
5864
setAddressableAreaId(addressableAreaId)
65+
const foundFixtureId =
66+
deckConfig.find(config => config.cutoutId === cutoutId)
67+
?.cutoutFixtureId ?? null
68+
setExistingCutoutFixtureId(foundFixtureId ?? null)
5969
}
6070

6171
const removeFixtureFromCutout = (
@@ -124,6 +134,7 @@ export function useDeckConfigurationEditingTools(
124134
}}
125135
isOnDevice={isOnDevice}
126136
deckDef={deckDef}
137+
existingCutoutFixtureId={existingCutoutFixtureId ?? undefined}
127138
/>
128139
) : null,
129140
}

shared-data/js/constants.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -677,10 +677,11 @@ export const FLEX_USB_MODULE_FIXTURES: CutoutFixtureId[] = [
677677
FLEX_STACKER_V1_FIXTURE,
678678
]
679679

680-
export const MAGNETIC_BLOCK_FIXTURES: CutoutFixtureId[] = [
680+
export const MAGNETIC_BLOCK_FIXTURES: CutoutFixtureIdsWithFakes[] = [
681681
MAGNETIC_BLOCK_V1_FIXTURE,
682682
STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE,
683683
FLEX_STACKER_WITH_MAG_BLOCK_FIXTURE,
684+
FAKE_STAGING_SLOT_WITH_MAG_BLOCK,
684685
]
685686

686687
export const SINGLE_SLOT_FIXTURES: CutoutFixtureIdsWithFakes[] = [
@@ -730,13 +731,6 @@ export const FLEX_STACKER_FIXTURES: CutoutFixtureIdsWithFakes[] = [
730731
FLEX_STACKER_WTIH_WASTE_CHUTE_ADAPTER_NO_COVER_FIXTURE,
731732
]
732733

733-
export const COMBO_FIXTURES: CutoutFixtureIdsWithFakes[] = [
734-
...FLEX_STACKER_FIXTURES,
735-
...MAGNETIC_BLOCK_FIXTURES,
736-
...WASTE_CHUTE_FIXTURES,
737-
STAGING_AREA_RIGHT_SLOT_FIXTURE,
738-
]
739-
740734
export const DEFAULT_AA_FOR_WASTE_CHUTE = ONE_CHANNEL_WASTE_CHUTE_ADDRESSABLE_AREA
741735

742736
export const STAGING_AREA_FIXTURES: CutoutFixtureId[] = [
@@ -809,3 +803,11 @@ export const FAKE_FIXTURE_IDS: CutoutFixtureIdsWithFakes[] = [
809803
FAKE_STAGING_SLOT_WITH_MAG_BLOCK,
810804
FAKE_WASTE_CHUTE_WITH_EMPTY_SLOT,
811805
]
806+
807+
export const COMBO_FIXTURES: CutoutFixtureIdsWithFakes[] = [
808+
...FLEX_STACKER_FIXTURES,
809+
...MAGNETIC_BLOCK_FIXTURES,
810+
...WASTE_CHUTE_FIXTURES,
811+
...FAKE_FIXTURE_IDS,
812+
...STAGING_AREA_FIXTURES,
813+
]

0 commit comments

Comments
 (0)