From f93ac416187b5f14ce1769be6c44afabf9ce5a84 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Mon, 5 Aug 2024 11:21:35 -0400 Subject: [PATCH] fix(app): fix desktop post-run drop tip wiz crash after tip removal (#15887) Closes RQA-2902 Refactor useTipAttachementStatus to provide only one pipette with tip at a time. --- .../Devices/ProtocolRun/ProtocolRunHeader.tsx | 10 ++-- .../__tests__/ProtocolRunHeader.test.tsx | 20 ++++++-- .../DropTipWizardFlows/TipsAttachedModal.tsx | 11 +++-- .../__tests__/DropTipWizardFlows.test.tsx | 46 +++++++++---------- .../__tests__/TipsAttachedModal.test.tsx | 22 ++++----- .../organisms/DropTipWizardFlows/index.tsx | 20 ++++---- .../RecoveryOptions/ManageTips.tsx | 9 ++-- .../__tests__/ManageTips.test.tsx | 2 +- app/src/pages/RunSummary/index.tsx | 20 ++++---- 9 files changed, 83 insertions(+), 77 deletions(-) diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx index 9f4bef400ee..6a704c96699 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx @@ -205,7 +205,7 @@ export function ProtocolRunHeader({ determineTipStatus, resetTipStatus, setTipStatusResolved, - pipettesWithTip, + aPipetteWithTip, } = useTipAttachmentStatus({ runId, runRecord, @@ -421,7 +421,7 @@ export function ProtocolRunHeader({ ) : null} @@ -496,11 +496,11 @@ export function ProtocolRunHeader({ robotName={robotName} /> ) : null} - {showDTWiz && mostRecentRunId === runId ? ( + {showDTWiz && aPipetteWithTip != null ? ( setTipStatusResolved().then(toggleDTWiz)} /> ) : null} diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx index 157538c9ff8..70b16c61b55 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx @@ -14,7 +14,6 @@ import { RUN_STATUS_SUCCEEDED, RUN_STATUS_BLOCKED_BY_OPEN_DOOR, instrumentsResponseLeftPipetteFixture, - instrumentsResponseRightPipetteFixture, } from '@opentrons/api-client' import { useHost, @@ -88,6 +87,7 @@ import { useNotifyRunQuery, useCurrentRunId } from '../../../../resources/runs' import { useDropTipWizardFlows, useTipAttachmentStatus, + DropTipWizardFlows, } from '../../../DropTipWizardFlows' import { useErrorRecoveryFlows, @@ -340,10 +340,7 @@ describe('ProtocolRunHeader', () => { vi.mocked(useInstrumentsQuery).mockReturnValue({ data: {} } as any) vi.mocked(useHost).mockReturnValue({} as any) vi.mocked(useTipAttachmentStatus).mockReturnValue({ - pipettesWithTip: [ - instrumentsResponseLeftPipetteFixture, - instrumentsResponseRightPipetteFixture, - ], + aPipetteWithTip: instrumentsResponseLeftPipetteFixture, areTipsAttached: true, determineTipStatus: mockDetermineTipStatus, resetTipStatus: vi.fn(), @@ -384,6 +381,9 @@ describe('ProtocolRunHeader', () => { vi.mocked(ProtocolDropTipModal).mockReturnValue(
MOCK_DROP_TIP_MODAL
) + vi.mocked(DropTipWizardFlows).mockReturnValue( +
MOCK_DROP_TIP_WIZARD_FLOWS
+ ) }) afterEach(() => { @@ -1076,4 +1076,14 @@ describe('ProtocolRunHeader', () => { render() screen.getByText('MOCK_ERROR_RECOVERY') }) + + it('renders DropTipWizardFlows when conditions are met', () => { + vi.mocked(useDropTipWizardFlows).mockReturnValue({ + showDTWiz: true, + toggleDTWiz: vi.fn(), + }) + + render() + screen.getByText('MOCK_DROP_TIP_WIZARD_FLOWS') + }) }) diff --git a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx index 0cb1872b196..71de14567fa 100644 --- a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx +++ b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import capitalize from 'lodash/capitalize' -import head from 'lodash/head' import NiceModal, { useModal } from '@ebay/nice-modal-react' import { Trans, useTranslation } from 'react-i18next' @@ -23,7 +22,7 @@ import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' import type { PipetteWithTip } from '.' interface TipsAttachedModalProps { - pipettesWithTip: PipetteWithTip[] + aPipetteWithTip: PipetteWithTip host: HostConfig | null setTipStatusResolved: (onEmpty?: () => void) => Promise } @@ -38,11 +37,11 @@ export const handleTipsAttachedModal = ( const TipsAttachedModal = NiceModal.create( (props: TipsAttachedModalProps): JSX.Element => { - const { pipettesWithTip, host, setTipStatusResolved } = props + const { aPipetteWithTip, host, setTipStatusResolved } = props const { t } = useTranslation(['drop_tip_wizard']) const modal = useModal() - const { mount, specs } = head(pipettesWithTip) as PipetteWithTip + const { mount, specs } = aPipetteWithTip const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() const tipsAttachedHeader: ModalHeaderBaseProps = { @@ -57,7 +56,9 @@ const TipsAttachedModal = NiceModal.create( } const is96Channel = specs.channels === 96 - const displayMountText = is96Channel ? '96-Channel' : capitalize(mount) + const displayMountText = is96Channel + ? '96-Channel' + : capitalize(mount as string) return ( diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx index d0763e3e307..bd1cc918ea5 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx @@ -37,6 +37,21 @@ const MOCK_ACTUAL_PIPETTE = { }, } as PipetteModelSpecs +const mockPipetteWithTip: PipetteWithTip = { + mount: 'left', + specs: MOCK_ACTUAL_PIPETTE, +} + +const mockSecondPipetteWithTip: PipetteWithTip = { + mount: 'right', + specs: MOCK_ACTUAL_PIPETTE, +} + +const mockPipettesWithTip: PipetteWithTip[] = [ + mockPipetteWithTip, + mockSecondPipetteWithTip, +] + describe('useTipAttachmentStatus', () => { let mockGetPipettesWithTipAttached: Mock @@ -44,6 +59,7 @@ describe('useTipAttachmentStatus', () => { mockGetPipettesWithTipAttached = vi.mocked(getPipettesWithTipAttached) vi.mocked(getPipetteModelSpecs).mockReturnValue(MOCK_ACTUAL_PIPETTE) vi.mocked(DropTipWizard).mockReturnValue(
MOCK DROP TIP WIZ
) + mockGetPipettesWithTipAttached.mockResolvedValue(mockPipettesWithTip) }) afterEach(() => { @@ -54,16 +70,10 @@ describe('useTipAttachmentStatus', () => { const { result } = renderHook(() => useTipAttachmentStatus({} as any)) expect(result.current.areTipsAttached).toBe(false) - expect(result.current.pipettesWithTip).toEqual([]) + expect(result.current.aPipetteWithTip).toEqual(null) }) it('should determine tip status and update state accordingly', async () => { - const mockPipettesWithTip: PipetteWithTip[] = [ - { mount: 'left', specs: MOCK_ACTUAL_PIPETTE }, - { mount: 'right', specs: MOCK_ACTUAL_PIPETTE }, - ] - mockGetPipettesWithTipAttached.mockResolvedValueOnce(mockPipettesWithTip) - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) await act(async () => { @@ -71,15 +81,10 @@ describe('useTipAttachmentStatus', () => { }) expect(result.current.areTipsAttached).toBe(true) - expect(result.current.pipettesWithTip).toEqual(mockPipettesWithTip) + expect(result.current.aPipetteWithTip).toEqual(mockPipetteWithTip) }) it('should reset tip status', async () => { - const mockPipettesWithTip: PipetteWithTip[] = [ - { mount: 'left', specs: MOCK_ACTUAL_PIPETTE }, - ] - mockGetPipettesWithTipAttached.mockResolvedValueOnce(mockPipettesWithTip) - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) await act(async () => { @@ -88,16 +93,10 @@ describe('useTipAttachmentStatus', () => { }) expect(result.current.areTipsAttached).toBe(false) - expect(result.current.pipettesWithTip).toEqual([]) + expect(result.current.aPipetteWithTip).toEqual(null) }) it('should set tip status resolved and update state', async () => { - const mockPipettesWithTip: PipetteWithTip[] = [ - { mount: 'left', specs: MOCK_ACTUAL_PIPETTE }, - { mount: 'right', specs: MOCK_ACTUAL_PIPETTE }, - ] - mockGetPipettesWithTipAttached.mockResolvedValueOnce(mockPipettesWithTip) - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) await act(async () => { @@ -105,14 +104,11 @@ describe('useTipAttachmentStatus', () => { result.current.setTipStatusResolved() }) - expect(result.current.pipettesWithTip).toEqual([mockPipettesWithTip[1]]) + expect(result.current.aPipetteWithTip).toEqual(mockSecondPipetteWithTip) }) it('should call onEmptyCache callback when cache becomes empty', async () => { - const mockPipettesWithTip: PipetteWithTip[] = [ - { mount: 'left', specs: MOCK_ACTUAL_PIPETTE }, - ] - mockGetPipettesWithTipAttached.mockResolvedValueOnce(mockPipettesWithTip) + mockGetPipettesWithTipAttached.mockResolvedValueOnce([mockPipetteWithTip]) const onEmptyCacheMock = vi.fn() const { result } = renderHook(() => useTipAttachmentStatus({} as any)) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx index 135ff4e0e6e..edd24d50e10 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx @@ -33,24 +33,24 @@ const ninetySixSpecs = { channels: 96, } as PipetteModelSpecs -const MOCK_PIPETTES_WITH_TIP: PipetteWithTip[] = [ - { mount: LEFT, specs: MOCK_ACTUAL_PIPETTE }, -] -const MOCK_96_WITH_TIP: PipetteWithTip[] = [ - { mount: LEFT, specs: ninetySixSpecs }, -] +const MOCK_A_PIPETTE_WITH_TIP: PipetteWithTip = { + mount: LEFT, + specs: MOCK_ACTUAL_PIPETTE, +} + +const MOCK_96_WITH_TIP: PipetteWithTip = { mount: LEFT, specs: ninetySixSpecs } const mockSetTipStatusResolved = vi.fn() const MOCK_HOST: HostConfig = { hostname: 'MOCK_HOST' } -const render = (pipettesWithTips: PipetteWithTip[]) => { +const render = (aPipetteWithTip: PipetteWithTip) => { return renderWithProviders(