Skip to content

Commit

Permalink
refactor(app): add InterventionModal to Error Recovery (#15277)
Browse files Browse the repository at this point in the history
Closes EXEC-464

Adds the InterventionModal molecule to recovery flows.
  • Loading branch information
mjhuff authored May 29, 2024
1 parent 13f12c0 commit 58a43e7
Show file tree
Hide file tree
Showing 18 changed files with 220 additions and 211 deletions.
1 change: 1 addition & 0 deletions app/src/assets/localization/en/error_recovery.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
"stand_back_resuming": "Stand back, resuming current step",
"stand_back_retrying": "Stand back, retrying current command",
"tip_not_detected": "Tip not detected",
"view_error_details": "View error details",
"view_recovery_options": "View recovery options"
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
import * as React from 'react'
import { describe, it, expect, beforeEach } from 'vitest'
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { when } from 'vitest-when'
import '@testing-library/jest-dom/vitest'
import { screen } from '@testing-library/react'

import { screen, fireEvent } from '@testing-library/react'
import { COLORS, BORDERS } from '@opentrons/components'

import { i18n } from '../../../i18n'
import { renderWithProviders } from '../../../__testing-utils__'
import { getIsOnDevice } from '../../../redux/config'

import { InterventionModal } from '../'

import type { ModalType } from '../'
import type { State } from '../../../redux/types'

vi.mock('../../../redux/config')

const MOCK_STATE: State = {
config: {
isOnDevice: false,
},
} as any

const render = (props: React.ComponentProps<typeof InterventionModal>) => {
return renderWithProviders(<InterventionModal {...props} />)[0]
return renderWithProviders(<InterventionModal {...props} />, {
i18nInstance: i18n,
initialState: MOCK_STATE,
})[0]
}

describe('InterventionModal', () => {
let props: React.ComponentProps<typeof InterventionModal>

beforeEach(() => {
props = {
heading: 'mock intervention heading',
iconHeading: 'mock intervention icon heading',
children: 'mock intervention children',
iconName: 'alert-circle',
type: 'intervention-required',
}
when(vi.mocked(getIsOnDevice)).calledWith(MOCK_STATE).thenReturn(false)
})
;(['intervention-required', 'error'] as ModalType[]).forEach(type => {
const color =
Expand All @@ -45,7 +65,7 @@ describe('InterventionModal', () => {
it('renders passed elements', () => {
render(props)
screen.getByText('mock intervention children')
screen.getByText('mock intervention heading')
screen.getByText('mock intervention icon heading')
})
it('renders an icon if an icon is specified', () => {
const { container } = render(props)
Expand All @@ -63,4 +83,39 @@ describe('InterventionModal', () => {
)
expect(icon).toBeNull()
})

it('renders title heading text if passed', () => {
props = { ...props, titleHeading: 'mock intervention title heading' }
render(props)
screen.getByText('mock intervention title heading')
})

it('fires an onClick when clicking on the iconHeading if passed', () => {
const mockOnClick = vi.fn()
props = { ...props, iconHeadingOnClick: mockOnClick }
render(props)

fireEvent.click(screen.getByText('mock intervention icon heading'))

expect(mockOnClick).toHaveBeenCalled()
})

it('renders the alternative desktop style when isOnDevice is false', () => {
render(props)

expect(screen.getByTestId('__otInterventionModal')).toHaveStyle({
width: '47rem',
maxHeight: '100%',
})
})

it('renders the alternative ODD style when isOnDevice is true', () => {
when(vi.mocked(getIsOnDevice)).calledWith(MOCK_STATE).thenReturn(true)
render(props)

expect(screen.getByTestId('__otInterventionModal')).toHaveStyle({
width: '62rem',
height: '35.5rem',
})
})
})
56 changes: 44 additions & 12 deletions app/src/molecules/InterventionModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as React from 'react'
import { useSelector } from 'react-redux'

import {
ALIGN_CENTER,
BORDERS,
Expand All @@ -7,6 +9,7 @@ import {
Flex,
Icon,
JUSTIFY_CENTER,
JUSTIFY_SPACE_BETWEEN,
OVERFLOW_AUTO,
POSITION_ABSOLUTE,
POSITION_RELATIVE,
Expand All @@ -15,6 +18,8 @@ import {
} from '@opentrons/components'
import type { IconName } from '@opentrons/components'

import { getIsOnDevice } from '../../redux/config'

export type ModalType = 'intervention-required' | 'error'

const BASE_STYLE = {
Expand All @@ -32,20 +37,29 @@ const BASE_STYLE = {

const BORDER_STYLE_BASE = `6px ${BORDERS.styleSolid}`

const MODAL_STYLE = {
const MODAL_BASE_STYLE = {
backgroundColor: COLORS.white,
position: POSITION_RELATIVE,
overflowY: OVERFLOW_AUTO,
maxHeight: '100%',
width: '47rem',
borderRadius: BORDERS.borderRadius8,
boxShadow: BORDERS.smallDropShadow,
'data-testid': '__otInterventionModal',
} as const

const MODAL_DESKTOP_STYLE = {
...MODAL_BASE_STYLE,
maxHeight: '100%',
width: '47rem',
} as const

const MODAL_ODD_STYLE = {
...MODAL_BASE_STYLE,
width: '62rem',
height: '35.5rem',
} as const

const HEADER_STYLE = {
alignItems: ALIGN_CENTER,
gridGap: SPACING.spacing12,
padding: `${SPACING.spacing20} ${SPACING.spacing32}`,
color: COLORS.white,
position: POSITION_STICKY,
Expand All @@ -69,8 +83,12 @@ const INTERVENTION_REQUIRED_COLOR = COLORS.blue50
const ERROR_COLOR = COLORS.red50

export interface InterventionModalProps {
/** optional modal heading **/
heading?: React.ReactNode
/** Optional modal title heading. Aligned to the left. */
titleHeading?: React.ReactNode
/** Optional modal heading right of the icon. Aligned right if titleHeading is supplied, otherwise aligned left. **/
iconHeading?: React.ReactNode
/** Optional onClick for the icon heading and icon. */
iconHeadingOnClick?: () => void
/** overall style hint */
type?: ModalType
/** optional icon name */
Expand All @@ -86,21 +104,35 @@ export function InterventionModal(props: InterventionModalProps): JSX.Element {
const border = `${BORDER_STYLE_BASE} ${
modalType === 'error' ? ERROR_COLOR : INTERVENTION_REQUIRED_COLOR
}`
const headerJustifyContent =
props.titleHeading != null ? JUSTIFY_SPACE_BETWEEN : undefined

const isOnDevice = useSelector(getIsOnDevice)
const modalStyle = isOnDevice ? MODAL_ODD_STYLE : MODAL_DESKTOP_STYLE

return (
<Flex {...WRAPPER_STYLE}>
<Flex {...BASE_STYLE} zIndex={10}>
<Box
{...MODAL_STYLE}
{...modalStyle}
border={border}
onClick={(e: React.MouseEvent) => {
e.stopPropagation()
}}
>
<Flex {...HEADER_STYLE} backgroundColor={headerColor}>
{props.iconName != null ? (
<Icon name={props.iconName} size={SPACING.spacing32} />
) : null}
{props.heading != null ? props.heading : null}
<Flex
{...HEADER_STYLE}
backgroundColor={headerColor}
justifyContent={headerJustifyContent}
onClick={props.iconHeadingOnClick}
>
{props.titleHeading}
<Flex alignItems={ALIGN_CENTER} gridGap={SPACING.spacing12}>
{props.iconName != null ? (
<Icon name={props.iconName} size={SPACING.spacing32} />
) : null}
{props.iconHeading != null ? props.iconHeading : null}
</Flex>
</Flex>
{props.children}
</Box>
Expand Down
12 changes: 3 additions & 9 deletions app/src/organisms/ErrorRecoveryFlows/BeforeBeginning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { Trans, useTranslation } from 'react-i18next'
import {
DIRECTION_COLUMN,
Flex,
JUSTIFY_SPACE_BETWEEN,
SPACING,
JUSTIFY_CENTER,
StyledText,
} from '@opentrons/components'
Expand All @@ -16,6 +14,7 @@ import {
BODY_TEXT_STYLE,
ODD_SECTION_TITLE_STYLE,
} from './constants'
import { RecoverySingleColumnContent } from './shared'

import type { RecoveryContentProps } from './types'

Expand All @@ -28,12 +27,7 @@ export function BeforeBeginning({

if (isOnDevice) {
return (
<Flex
padding={SPACING.spacing32}
flexDirection={DIRECTION_COLUMN}
justifyContent={JUSTIFY_SPACE_BETWEEN}
height="100%"
>
<RecoverySingleColumnContent>
<Flex flexDirection={DIRECTION_COLUMN} height="100%">
<StyledText css={ODD_SECTION_TITLE_STYLE} as="h4SemiBold">
{t('before_you_begin')}
Expand All @@ -52,7 +46,7 @@ export function BeforeBeginning({
marginTop="auto"
/>
</Flex>
</Flex>
</RecoverySingleColumnContent>
)
} else {
return null
Expand Down
90 changes: 0 additions & 90 deletions app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryHeader.tsx

This file was deleted.

Loading

0 comments on commit 58a43e7

Please sign in to comment.