Skip to content

Commit

Permalink
feat(app): add aria-disabled for displaying snack bar (#15581)
Browse files Browse the repository at this point in the history
* feat(app): add aria-disabled for displaying snack bar
  • Loading branch information
koji authored Jul 9, 2024
1 parent 1853523 commit c40d293
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 28 deletions.
1 change: 1 addition & 0 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"proceed_to_run": "Proceed to run",
"protocol_analysis_failed": "Protocol analysis failed",
"protocol_can_be_closed": "This protocol can now be closed.",
"protocol_requires_csv": "This protocol requires a CSV file. Tap the CSV row below to select one.",
"protocol_run_canceled": "Protocol run canceled.",
"protocol_run_complete": "Protocol run complete.",
"protocol_run_failed": "Protocol run failed.",
Expand Down
16 changes: 15 additions & 1 deletion app/src/atoms/buttons/SmallButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ interface SmallButtonProps extends StyleProps {
iconName?: IconName | null
buttonCategory?: ButtonCategory // if not specified, it will be 'default'
disabled?: boolean
/** aria-disabled for displaying snack bar, used for ODD only at this time. */
ariaDisabled?: boolean
}

export function SmallButton(props: SmallButtonProps): JSX.Element {
Expand All @@ -44,6 +46,7 @@ export function SmallButton(props: SmallButtonProps): JSX.Element {
disabled,
iconPlacement,
iconName,
ariaDisabled = false,
...buttonProps
} = props

Expand All @@ -64,27 +67,31 @@ export function SmallButton(props: SmallButtonProps): JSX.Element {
disabledBackgroundColor: `${COLORS.grey35}`,
disabledColor: `${COLORS.grey50}`,
},

alert: {
defaultColor: COLORS.white,
defaultBackgroundColor: COLORS.red50,
activeBackgroundColor: COLORS.red55,
disabledBackgroundColor: `${COLORS.grey35}`,
disabledColor: `${COLORS.grey50}`,
},

primary: {
defaultColor: COLORS.white,
defaultBackgroundColor: COLORS.blue50,
activeBackgroundColor: COLORS.blue60,
disabledBackgroundColor: `${COLORS.grey35}`,
disabledColor: `${COLORS.grey50}`,
},

tertiaryHighLight: {
defaultColor: COLORS.black90,
defaultBackgroundColor: `${COLORS.blue50}00`,
activeBackgroundColor: `${COLORS.grey35}`,
disabledBackgroundColor: `${COLORS.blue50}00`,
disabledColor: `${COLORS.grey50}`,
},

tertiaryLowLight: {
defaultColor: `${COLORS.grey60}`,
defaultBackgroundColor: ` ${COLORS.blue50}00`,
Expand Down Expand Up @@ -133,18 +140,25 @@ export function SmallButton(props: SmallButtonProps): JSX.Element {
.disabledBackgroundColor};
color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType].disabledColor};
}
&[aria-disabled='true'] {
background-color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType]
.disabledBackgroundColor};
color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType].disabledColor};
}
`

return (
<Btn
css={SMALL_BUTTON_STYLE}
disabled={disabled}
disabled={ariaDisabled ? false : disabled}
padding={
iconPlacement != null
? SPACING.spacing16
: `${SPACING.spacing16} ${SPACING.spacing24}`
}
{...buttonProps}
aria-disabled={ariaDisabled}
>
<Flex
flexDirection={DIRECTION_ROW}
Expand Down
34 changes: 34 additions & 0 deletions app/src/atoms/buttons/__tests__/SmallButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('SmallButton', () => {
buttonText: 'small button',
}
})

it('renders the primary button and it works as expected', () => {
render(props)
fireEvent.click(screen.getByText('small button'))
Expand All @@ -41,6 +42,7 @@ describe('SmallButton', () => {
`background-color: ${COLORS.red50}`
)
})

it('renders the secondary button', () => {
props = {
...props,
Expand All @@ -51,6 +53,7 @@ describe('SmallButton', () => {
`background-color: ${COLORS.blue35}`
)
})

it('renders the tertiary high light button', () => {
props = {
...props,
Expand All @@ -59,6 +62,7 @@ describe('SmallButton', () => {
render(props)
expect(screen.getByRole('button')).toHaveStyle(`color: ${COLORS.black90}`)
})

it('renders the tertiary low light', () => {
props = {
...props,
Expand All @@ -67,6 +71,7 @@ describe('SmallButton', () => {
render(props)
expect(screen.getByRole('button')).toHaveStyle(`color: ${COLORS.grey60}`)
})

it('renders the button as disabled', () => {
props = {
...props,
Expand All @@ -75,6 +80,7 @@ describe('SmallButton', () => {
render(props)
expect(screen.getByRole('button')).toBeDisabled()
})

it('renders the rounded button category', () => {
props = {
...props,
Expand All @@ -85,6 +91,7 @@ describe('SmallButton', () => {
`border-radius: ${BORDERS.borderRadius40}`
)
})

it('renders an icon with start placement', () => {
props = {
...props,
Expand All @@ -103,4 +110,31 @@ describe('SmallButton', () => {
render(props)
screen.getByLabelText('alert')
})

it('should render disabled style when ariaDisabled is true', () => {
props = {
...props,
ariaDisabled: true,
}
render(props)
expect(screen.getByRole('button')).toHaveStyle(
`background-color: ${COLORS.grey35}`
)
expect(screen.getByRole('button')).toHaveStyle(`color: ${COLORS.grey50}`)
})

it('should not render disabled style when ariaDisabled is false and disabled is false', () => {
props = {
...props,
disabled: false,
ariaDisabled: false,
}
render(props)
expect(screen.getByRole('button')).not.toHaveStyle(
`background-color: ${COLORS.grey35}`
)
expect(screen.getByRole('button')).not.toHaveStyle(
`color: ${COLORS.grey50}`
)
})
})
12 changes: 12 additions & 0 deletions app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const secondaryButtonProps: React.ComponentProps<typeof SmallButton> = {
buttonType: 'tertiaryLowLight',
iconName: 'information',
iconPlacement: 'startIcon',
ariaDisabled: false,
}

export const TitleWithTwoButtons: Story = {
Expand All @@ -75,3 +76,14 @@ export const TitleWithTwoButtons: Story = {
onClickBack: () => {},
},
}

export const TitleWithTwoButtonsDisabled: Story = {
args: {
header: 'Header',
buttonText: 'ButtonText',
onClickButton: () => {},
secondaryButtonProps,
onClickBack: () => {},
ariaDisabled: true,
},
}
3 changes: 3 additions & 0 deletions app/src/organisms/ChildNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface ChildNavigationProps extends StyleProps {
iconName?: IconName
iconPlacement?: IconPlacement
secondaryButtonProps?: React.ComponentProps<typeof SmallButton>
ariaDisabled?: boolean
}

export function ChildNavigation({
Expand All @@ -51,6 +52,7 @@ export function ChildNavigation({
iconPlacement,
secondaryButtonProps,
buttonIsDisabled,
ariaDisabled = false,
...styleProps
}: ChildNavigationProps): JSX.Element {
return (
Expand Down Expand Up @@ -98,6 +100,7 @@ export function ChildNavigation({
iconPlacement={iconPlacement}
disabled={buttonIsDisabled}
data-testid="ChildNavigation_Primary_Button"
ariaDisabled={ariaDisabled}
/>
</Flex>
) : null}
Expand Down
4 changes: 2 additions & 2 deletions app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export function ChooseCsvFile({
csvFilesOnRobot.map((csv: CsvFileData) => (
<RadioButton
key={csv.id}
data-testid={`${csv.id}`}
data-testid={csv.id}
buttonLabel={csv.name}
buttonValue={`${csv.id}`}
buttonValue={csv.id}
onChange={() => {}}
/>
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import {
useCreateRunMutation,
useHost,
} from '@opentrons/react-api-client'
import { COLORS } from '@opentrons/components'

import { i18n } from '../../../i18n'
import { renderWithProviders } from '../../../__testing-utils__'
import { ChooseEnum } from '../ChooseEnum'
import { ChooseNumber } from '../ChooseNumber'
import { ChooseCsvFile } from '../ChooseCsvFile'
import { mockRunTimeParameterData } from '../../../pages/ProtocolDetails/fixtures'
import { useToaster } from '../../ToasterOven'
import { useFeatureFlag } from '../../../redux/config'
import { ProtocolSetupParameters } from '..'

Expand All @@ -26,6 +29,7 @@ vi.mock('../ChooseEnum')
vi.mock('../ChooseNumber')
vi.mock('../ChooseCsvFile')
vi.mock('../../../redux/config')
vi.mock('../../ToasterOven')
vi.mock('@opentrons/react-api-client')
vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis')
vi.mock('react-router-dom', async importOriginal => {
Expand All @@ -44,6 +48,7 @@ const mockMostRecentAnalysis = ({
commands: [],
labware: [],
} as unknown) as CompletedProtocolAnalysis
const mockMakeSnackbar = vi.fn()

const render = (
props: React.ComponentProps<typeof ProtocolSetupParameters>
Expand Down Expand Up @@ -77,6 +82,11 @@ describe('ProtocolSetupParameters', () => {
when(vi.mocked(useFeatureFlag))
.calledWith('enableCsvFile')
.thenReturn(false)
vi.mocked(useToaster).mockReturnValue({
makeSnackbar: mockMakeSnackbar,
makeToast: vi.fn(),
eatToast: vi.fn(),
})
})

it('renders the parameters labels and mock data', () => {
Expand All @@ -100,13 +110,12 @@ describe('ProtocolSetupParameters', () => {
screen.getByText('mock ChooseNumber')
})

// ToDo (kk:06/18/2024) comment-out will be removed in a following PR.
// it('renders the ChooseCsvFile component when a str param is selected', () => {
// vi.mocked(useFeatureFlag).mockReturnValue(true)
// render(props)
// fireEvent.click(screen.getByText('CSV File'))
// screen.getByText('mock ChooseCsvFile')
// })
it('renders the ChooseCsvFile component when a str param is selected', () => {
vi.mocked(useFeatureFlag).mockReturnValue(true)
render(props)
fireEvent.click(screen.getByText('CSV File'))
screen.getByText('mock ChooseCsvFile')
})

it('renders the other setting when boolean param is selected', () => {
render(props)
Expand Down Expand Up @@ -166,7 +175,7 @@ describe('ProtocolSetupParameters', () => {
expect(title).not.toBeInTheDocument()
})

it('render csv file when a protocol requires a csv file', () => {
it('render csv file when a protocol requires a csv file and confirm values button has the disabled style', () => {
when(vi.mocked(useFeatureFlag)).calledWith('enableCsvFile').thenReturn(true)
const mockMostRecentAnalysisForCsv = ({
commands: [],
Expand All @@ -181,10 +190,27 @@ describe('ProtocolSetupParameters', () => {
screen.getByText('CSV File')
screen.getByText('Required')
const button = screen.getByRole('button', { name: 'Confirm values' })
expect(button).toBeDisabled()
expect(button).toHaveStyle(`background-color: ${COLORS.grey35}`)
expect(button).toHaveStyle(`color: ${COLORS.grey50}`)
})

it.todo(
'render csv file name when a protocol analysis result is not parameter-value-required'
)
it('when tapping aria-disabled button, snack bar will show up', () => {
when(vi.mocked(useFeatureFlag)).calledWith('enableCsvFile').thenReturn(true)
const mockMostRecentAnalysisForCsv = ({
commands: [],
labware: [],
result: 'parameter-value-required',
} as unknown) as CompletedProtocolAnalysis
render({
...props,
runTimeParameters: mockRunTimeParameterData,
mostRecentAnalysis: mockMostRecentAnalysisForCsv,
})

const button = screen.getByRole('button', { name: 'Confirm values' })
fireEvent.click(button)
expect(mockMakeSnackbar).toBeCalledWith(
'This protocol requires a CSV file. Tap the CSV row below to select one.'
)
})
})
Loading

0 comments on commit c40d293

Please sign in to comment.