diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx index e20b1c8f05b..6a3d26f2af8 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx @@ -16,9 +16,11 @@ import { useLEDLights } from '../../hooks' interface EnableStatusLightProps { robotName: string + isEstopNotDisengaged: boolean } export function EnableStatusLight({ robotName, + isEstopNotDisengaged, }: EnableStatusLightProps): JSX.Element { const { t } = useTranslation('device_settings') const { lightsEnabled, toggleLights } = useLEDLights(robotName) @@ -46,6 +48,7 @@ export function EnableStatusLight({ toggledOn={lightsEnabled} onClick={toggleLights} id="RobotSettings_enableStatusLightToggleButton" + disabled={isEstopNotDisengaged} /> ) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx index 5097900fd8c..23778d16b4d 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx @@ -24,10 +24,12 @@ const JUPYTER_NOTEBOOK_LINK = export interface OpenJupyterControlProps { robotIp: string + isEstopNotDisengaged: boolean } export function OpenJupyterControl({ robotIp, + isEstopNotDisengaged, }: OpenJupyterControlProps): JSX.Element { const { t } = useTranslation('device_settings') const href = `http://${robotIp}:48888` @@ -51,8 +53,8 @@ export function OpenJupyterControl({ trackEvent(EVENT_JUPYTER_OPEN)} - as={Link} href={href} marginLeft={SPACING.spacing32} external diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx index 526c6e30624..c5476583d90 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx @@ -27,10 +27,12 @@ import type { IconProps } from '@opentrons/components' interface TroubleshootingProps { robotName: string + isEstopNotDisengaged: boolean } export function Troubleshooting({ robotName, + isEstopNotDisengaged, }: TroubleshootingProps): JSX.Element { const { t } = useTranslation('device_settings') const robot = useRobot(robotName) @@ -125,7 +127,10 @@ export function Troubleshooting({ { beforeEach(() => { props = { robotName: ROBOT_NAME, + isEstopNotDisengaged: false, } mockUseLEDLights.mockReturnValue({ lightsEnabled: false, @@ -47,4 +48,13 @@ describe('EnableStatusLight', () => { getByLabelText('enable_status_light').click() expect(mockToggleLights).toHaveBeenCalled() }) + + it('shoud make toggle button disabled when e-stop is pressed', () => { + props = { + ...props, + isEstopNotDisengaged: true, + } + const [{ getByLabelText }] = render(props) + expect(getByLabelText('enable_status_light')).toBeDisabled() + }) }) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx index 64d7b5a1ddd..f279c1d85d1 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx @@ -17,17 +17,22 @@ const mockIpAddress = '1.1.1.1' const mockLink = `http://${mockIpAddress}:48888` const trackEvent = jest.fn() -const render = () => { +const render = (props: React.ComponentProps) => { return renderWithProviders( - + , { i18nInstance: i18n } ) } describe('RobotSettings OpenJupyterControl', () => { + let props: React.ComponentProps beforeEach(() => { + props = { + robotIp: mockIpAddress, + isEstopNotDisengaged: false, + } mockUseTrackEvent.mockReturnValue(trackEvent) }) @@ -36,7 +41,7 @@ describe('RobotSettings OpenJupyterControl', () => { }) it('should render title, description and button', () => { - const [{ getByText, getByRole }] = render() + const [{ getByText, getByRole }] = render(props) getByText('Jupyter Notebook') getByText( 'Open the Jupyter Notebook running on this robot in the web browser. This is an experimental feature.' @@ -44,25 +49,31 @@ describe('RobotSettings OpenJupyterControl', () => { getByText('Learn more about using Jupyter notebook') getByText('Launch Jupyter Notebook') expect( - getByRole('link', { name: 'Launch Jupyter Notebook' }) + getByRole('button', { name: 'Launch Jupyter Notebook' }) ).toBeInTheDocument() }) it('should render jupyter notebook link', () => { - const [{ getByText }] = render() - const link = getByText('Launch Jupyter Notebook') - expect(link.closest('a')).toHaveAttribute('href', mockLink) - expect(link.closest('a')).toHaveAttribute('target', '_blank') - expect(link.closest('a')).toHaveAttribute('rel', 'noopener noreferrer') + const [{ getByRole }] = render(props) + const link = getByRole('button', { name: 'Launch Jupyter Notebook' }) + console.log(link) + expect(link).toHaveAttribute('href', mockLink) }) it('should send and analytics event on link click', () => { - const [{ getByRole }] = render() - const button = getByRole('link', { name: 'Launch Jupyter Notebook' }) + const [{ getByRole }] = render(props) + const button = getByRole('button', { name: 'Launch Jupyter Notebook' }) fireEvent.click(button) expect(trackEvent).toHaveBeenCalledWith({ name: ANALYTICS_JUPYTER_OPEN, properties: {}, }) }) + + it('should render disabled button when e-stop button is pressed', () => { + props = { ...props, isEstopNotDisengaged: true } + const [{ getByRole }] = render(props) + const button = getByRole('button', { name: 'Launch Jupyter Notebook' }) + expect(button).toBeDisabled() + }) }) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx index 700ae868ee7..ec3ef5a93d7 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx @@ -16,7 +16,7 @@ import { useRobot } from '../../../hooks' import { Troubleshooting } from '../Troubleshooting' import type { HostConfig } from '@opentrons/api-client' -import { ToasterContextType } from '../../../../ToasterOven/ToasterContext' +import type { ToasterContextType } from '../../../../ToasterOven/ToasterContext' jest.mock('@opentrons/react-api-client') jest.mock('../../../../../organisms/ToasterOven') @@ -32,18 +32,25 @@ const HOST_CONFIG: HostConfig = { hostname: 'localhost' } const MOCK_MAKE_TOAST = jest.fn() const MOCK_EAT_TOAST = jest.fn() -const render = (robotName = ROBOT_NAME) => { +const render = (props: React.ComponentProps) => { return renderWithProviders( - + , { i18nInstance: i18n } ) } describe('RobotSettings Troubleshooting', () => { + let props: React.ComponentProps beforeEach(() => { - when(mockUseRobot).calledWith('otie').mockReturnValue(mockConnectableRobot) + props = { + robotName: ROBOT_NAME, + isEstopNotDisengaged: false, + } + when(mockUseRobot) + .calledWith(ROBOT_NAME) + .mockReturnValue(mockConnectableRobot) when(mockUseHost).calledWith().mockReturnValue(HOST_CONFIG) when(mockUseToaster) .calledWith() @@ -58,7 +65,7 @@ describe('RobotSettings Troubleshooting', () => { cleanup() }) it('should render title, description, and button', () => { - const [{ getByText, getByRole, getByTestId }] = render() + const [{ getByText, getByRole, getByTestId }] = render(props) getByText('Troubleshooting') getByTestId('RobotSettings_Troubleshooting') getByRole('button', { name: 'Download logs' }) @@ -66,13 +73,13 @@ describe('RobotSettings Troubleshooting', () => { it('should be disabled when logs are not available', () => { when(mockUseRobot).calledWith('otie').mockReturnValue(mockUnreachableRobot) - const [{ getByRole }] = render() + const [{ getByRole }] = render(props) const downloadLogsButton = getByRole('button', { name: 'Download logs' }) expect(downloadLogsButton).toBeDisabled() }) it('should initiate log download when clicking Download logs button', async () => { - const [{ getByRole, queryByText }] = render() + const [{ getByRole, queryByText }] = render(props) const downloadLogsButton = getByRole('button', { name: 'Download logs' }) act(() => { downloadLogsButton.click() @@ -89,4 +96,10 @@ describe('RobotSettings Troubleshooting', () => { expect(downloadLogsButton).not.toBeDisabled() }) }) + + it('should make donwload button disabled when e-stop is pressed', () => { + props = { ...props, isEstopNotDisengaged: true } + const [{ getByRole }] = render(props) + expect(getByRole('button', { name: 'Download logs' })).toBeDisabled() + }) }) diff --git a/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx b/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx index 1161c707b3a..b1a6fc16410 100644 --- a/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx +++ b/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx @@ -41,6 +41,7 @@ import { DeviceResetModal } from './AdvancedTab/AdvancedTabSlideouts/DeviceReset import { handleUpdateBuildroot } from './UpdateBuildroot' import { UNREACHABLE } from '../../../redux/discovery' import { Portal } from '../../../App/portal' +import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' import type { State, Dispatch } from '../../../redux/types' import type { @@ -72,6 +73,7 @@ export function RobotSettingsAdvanced({ ] = React.useState(false) const isRobotBusy = useIsRobotBusy({ poll: true }) + const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName) const robot = useRobot(robotName) const isFlex = useIsFlex(robotName) @@ -149,7 +151,7 @@ export function RobotSettingsAdvanced({ @@ -161,7 +163,7 @@ export function RobotSettingsAdvanced({ )} @@ -169,28 +171,37 @@ export function RobotSettingsAdvanced({ {isFlex ? ( <> - + ) : null} - + handleUpdateBuildroot(robot)} /> - + {isFlex ? null : ( <> @@ -198,25 +209,25 @@ export function RobotSettingsAdvanced({ )} diff --git a/app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx b/app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx index 9223b07ddd5..97758e3f573 100644 --- a/app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx +++ b/app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx @@ -68,7 +68,7 @@ const mockGetRequestById = RobotApi.getRequestById as jest.MockedFunction< typeof RobotApi.getRequestById > -describe('', () => { +describe('SelectNetwork', () => { let dispatch: any let mockStore: any @@ -102,7 +102,11 @@ describe('', () => { render = () => { return mount( - , + , { wrappingComponent: Provider, wrappingComponentProps: { store: mockStore }, diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx index 48599a112b5..77a96f1837c 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx +++ b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx @@ -55,6 +55,8 @@ const mockCalibratedModule = { }, } +const ROBOT_NAME = 'mockRobot' + const render = ( props: React.ComponentProps ): ReturnType => { @@ -71,6 +73,7 @@ describe('ModuleCalibrationItems', () => { attachedModules: mockFetchModulesSuccessActionPayloadModules, updateRobotStatus: jest.fn(), formattedPipetteOffsetCalibrations: [], + robotName: ROBOT_NAME, } mockModuleCalibrationOverflowMenu.mockReturnValue(
mock ModuleCalibrationOverflowMenu
diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx index 1687b533f69..f51099ce1cf 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx +++ b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx @@ -1,12 +1,15 @@ import * as React from 'react' import { waitFor } from '@testing-library/react' import { renderWithProviders } from '@opentrons/components' +import { when, resetAllWhenMocks } from 'jest-when' import { i18n } from '../../../../i18n' import { ModuleWizardFlows } from '../../../ModuleWizardFlows' import { useChainLiveCommands } from '../../../../resources/runs/hooks' import { mockThermocyclerGen2 } from '../../../../redux/modules/__fixtures__' import { useRunStatuses } from '../../../Devices/hooks' +import { useIsEstopNotDisengaged } from '../../../../resources/devices/hooks/useIsEstopNotDisengaged' + import { ModuleCalibrationOverflowMenu } from '../ModuleCalibrationOverflowMenu' import type { Mount } from '@opentrons/components' @@ -15,6 +18,8 @@ jest.mock('@opentrons/react-api-client') jest.mock('../../../ModuleWizardFlows') jest.mock('../../../Devices/hooks') jest.mock('../../../../resources/runs/hooks') +jest.mock('../../../../resources/devices/hooks/useIsEstopNotDisengaged') + const mockPipetteOffsetCalibrations = [ { modelName: 'mockPipetteModelLeft', @@ -92,6 +97,9 @@ const mockUseRunStatuses = useRunStatuses as jest.MockedFunction< const mockUseChainLiveCommands = useChainLiveCommands as jest.MockedFunction< typeof useChainLiveCommands > +const mockUseIsEstopNotDisengaged = useIsEstopNotDisengaged as jest.MockedFunction< + typeof useIsEstopNotDisengaged +> const render = ( props: React.ComponentProps @@ -101,6 +109,8 @@ const render = ( }) } +const ROBOT_NAME = 'mockRobot' + describe('ModuleCalibrationOverflowMenu', () => { let props: React.ComponentProps let mockChainLiveCommands = jest.fn() @@ -111,6 +121,7 @@ describe('ModuleCalibrationOverflowMenu', () => { attachedModule: mockThermocyclerGen2, updateRobotStatus: jest.fn(), formattedPipetteOffsetCalibrations: mockPipetteOffsetCalibrations, + robotName: ROBOT_NAME, } mockChainLiveCommands = jest.fn() mockChainLiveCommands.mockResolvedValue(null) @@ -124,10 +135,14 @@ describe('ModuleCalibrationOverflowMenu', () => { mockUseChainLiveCommands.mockReturnValue({ chainLiveCommands: mockChainLiveCommands, } as any) + when(mockUseIsEstopNotDisengaged) + .calledWith(ROBOT_NAME) + .mockReturnValue(false) }) afterEach(() => { jest.clearAllMocks() + resetAllWhenMocks() }) it('should render overflow menu buttons - not calibrated', () => { @@ -290,4 +305,12 @@ describe('ModuleCalibrationOverflowMenu', () => { getByLabelText('ModuleCalibrationOverflowMenu').click() expect(getByText('Calibrate module')).toBeDisabled() }) + + it('should be disabled when e-stop button is pressed', () => { + when(mockUseIsEstopNotDisengaged) + .calledWith(ROBOT_NAME) + .mockReturnValue(true) + const [{ getByLabelText }] = render(props) + expect(getByLabelText('ModuleCalibrationOverflowMenu')).toBeDisabled() + }) }) diff --git a/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx b/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx index e9c5862346d..5fd3ba38da1 100644 --- a/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx +++ b/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx @@ -47,7 +47,7 @@ const BODY_STYLE = css` border-radius: 3px; ` -export interface RobotSettingsGripperCalibrationProps { +interface RobotSettingsGripperCalibrationProps { gripper: GripperData | null robotName: string } diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx index 69db828b53b..5334cef607b 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx +++ b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx @@ -1,15 +1,18 @@ import * as React from 'react' - +import { when, resetAllWhenMocks } from 'jest-when' import { renderWithProviders } from '@opentrons/components' import { i18n } from '../../../i18n' import { GripperWizardFlows } from '../../../organisms/GripperWizardFlows' import { formatLastCalibrated } from '../CalibrationDetails/utils' +import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' import { RobotSettingsGripperCalibration } from '../RobotSettingsGripperCalibration' + import type { GripperData } from '@opentrons/api-client' -import type { RobotSettingsGripperCalibrationProps } from '../RobotSettingsGripperCalibration' + jest.mock('../../../organisms/GripperWizardFlows') jest.mock('../CalibrationDetails/utils') +jest.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') const mockGripperWizardFlows = GripperWizardFlows as jest.MockedFunction< typeof GripperWizardFlows @@ -17,29 +20,55 @@ const mockGripperWizardFlows = GripperWizardFlows as jest.MockedFunction< const mockFormatLastCalibrated = formatLastCalibrated as jest.MockedFunction< typeof formatLastCalibrated > +const mockUseIsEstopNotDisengaged = useIsEstopNotDisengaged as jest.MockedFunction< + typeof useIsEstopNotDisengaged +> -let props = { - gripper: { - serialNumber: 'mockSerial123', - data: { - calibratedOffset: { - last_modified: '12345', - }, +const mockGripperData = { + serialNumber: 'mockSerial123', + data: { + calibratedOffset: { + last_modified: '12345', }, - } as GripperData, -} + }, +} as GripperData +const mockNotCalibratedGripper = { + serialNumber: 'mockSerial123', + data: { + calibratedOffset: { + last_modified: undefined, + }, + }, +} as GripperData +const ROBOT_NAME = 'mockRobot' -const render = (props: RobotSettingsGripperCalibrationProps) => { +const render = ( + props: React.ComponentProps +) => { return renderWithProviders(, { i18nInstance: i18n, }) } describe('RobotSettingsGripperCalibration', () => { + let props: React.ComponentProps beforeEach(() => { mockFormatLastCalibrated.mockReturnValue('last calibrated 1/2/3') mockGripperWizardFlows.mockReturnValue(<>Mock Wizard Flow) + when(mockUseIsEstopNotDisengaged) + .calledWith(ROBOT_NAME) + .mockReturnValue(false) + props = { + gripper: mockGripperData, + robotName: ROBOT_NAME, + } }) + + afterEach(() => { + jest.clearAllMocks() + resetAllWhenMocks() + }) + it('renders a title and description - Gripper Calibration section', () => { const [{ getByText }] = render(props) getByText('Gripper Calibration') @@ -60,16 +89,7 @@ describe('RobotSettingsGripperCalibration', () => { getByText('Recalibrate gripper') }) it('renders not calibrated and calibrate button if calibration data does not exist', () => { - props = { - gripper: { - serialNumber: 'mockSerial123', - data: { - calibratedOffset: { - last_modified: undefined, - }, - }, - } as GripperData, - } + props = { ...props, gripper: mockNotCalibratedGripper } const [{ getByText, getByRole }] = render(props) getByText('mockSerial123') @@ -81,6 +101,7 @@ describe('RobotSettingsGripperCalibration', () => { getByText('Calibrate gripper') }) it('renders gripper wizard flows when calibrate is pressed', () => { + props = { ...props, gripper: mockNotCalibratedGripper } const [{ getByText, getByRole }] = render(props) const overflowButton = getByRole('button', { name: 'CalibrationOverflowMenu_button_gripperCalibration', @@ -92,10 +113,20 @@ describe('RobotSettingsGripperCalibration', () => { }) it('render text when gripper is not attached instead calibration data', () => { - props = { - gripper: null as any, - } + props = { ...props, gripper: null } const [{ getByText }] = render(props) getByText('No gripper attached') }) + + it('overflow menu is disabled when e-stop button is pressed', () => { + when(mockUseIsEstopNotDisengaged) + .calledWith(ROBOT_NAME) + .mockReturnValue(true) + const [{ getByRole }] = render(props) + expect( + getByRole('button', { + name: 'CalibrationOverflowMenu_button_gripperCalibration', + }) + ).toBeDisabled() + }) }) diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx index 88ac44042cf..da2adb6af9f 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx +++ b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx @@ -20,6 +20,8 @@ const render = ( }) } +const ROBOT_NAME = 'mockRobot' + describe('RobotSettingsModuleCalibration', () => { let props: React.ComponentProps @@ -28,6 +30,7 @@ describe('RobotSettingsModuleCalibration', () => { attachedModules: mockFetchModulesSuccessActionPayloadModules, updateRobotStatus: jest.fn(), formattedPipetteOffsetCalibrations: [], + robotName: ROBOT_NAME, } mockModuleCalibrationItems.mockReturnValue(
mock ModuleCalibrationItems