Skip to content

Commit

Permalink
fix(app): Update receiving new protocol toast (#13326)
Browse files Browse the repository at this point in the history
* fix(app): update receiving new protocol toast

Adds a 'close' button to the receiving protocol toast and prevents the toast from closing until a
user taps anywhere on the toast. Additionally, all toasts are set 32px from the bottom of the ODD
display.

partially closes RAUT-485

* fix(app): update toast stacking behavior

Partially closes RAUT-485. All ODD toasts now properly play exit animations. Toasts on the ODD have
the option not to stack.

* Fix exitNow prop test

* fix(app): enable hardware acceleration amd optimizations on toast animations

* Old toasts auto fade out when a second toast enters

* Toast refactor

* Delete ternary block
  • Loading branch information
mjhuff authored Aug 17, 2023
1 parent a882af1 commit e88397d
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 113 deletions.
8 changes: 6 additions & 2 deletions app/src/App/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function useProtocolReceiptToast(): void {
const protocolIdsRef = React.useRef(protocolIds)
const hasRefetched = React.useRef(true)

if (protocolIdsQuery.isRefetching === true) {
if (protocolIdsQuery.isRefetching) {
hasRefetched.current = false
}

Expand Down Expand Up @@ -83,7 +83,11 @@ export function useProtocolReceiptToast(): void {
t('protocol_added', {
protocol_name: name,
}),
'success'
'success',
{
closeButton: true,
disableTimeout: true,
}
)
})
})
Expand Down
69 changes: 33 additions & 36 deletions app/src/atoms/Toast/__tests__/ODDToast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { act, fireEvent } from '@testing-library/react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../i18n'
import { Toast } from '..'
import { Toast, TOAST_ANIMATION_DURATION } from '..'

const render = (props: React.ComponentProps<typeof Toast>) => {
return renderWithProviders(<Toast {...props} displayType="odd" />, {
Expand All @@ -21,6 +21,8 @@ describe('Toast', () => {
closeButton: true,
buttonText: 'Close',
onClose: jest.fn(),
displayType: 'odd',
exitNow: false,
}
})
afterEach(() => {
Expand All @@ -34,13 +36,8 @@ describe('Toast', () => {
})
it('truncates heading message whern too long', () => {
props = {
id: '1',
message: 'test message',
...props,
heading: 'Super-long-protocol-file-name-that-the-user-made.py',
type: 'success',
closeButton: true,
buttonText: 'Close',
onClose: jest.fn(),
}
const { getByText } = render(props)
getByText('Super-long-protocol-file-name-that-the-u...py')
Expand All @@ -51,24 +48,16 @@ describe('Toast', () => {
fireEvent.click(closeButton)
expect(props.onClose).toHaveBeenCalled()
})
it('does not render close button if prop is undefined', () => {
it('does not render close button if buttonText and closeButton are undefined', () => {
props = {
id: '1',
message: 'test message',
type: 'success',
closeButton: false,
onClose: jest.fn(),
...props,
buttonText: undefined,
closeButton: undefined,
}
const { queryByRole } = render(props)
expect(queryByRole('button')).toBeNull()
})
it('should have success styling when passing success as type', () => {
props = {
id: '1',
message: 'test message',
type: 'success',
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const successToast = getByTestId('Toast_success')
expect(successToast).toHaveStyle(`color: #04aa65
Expand All @@ -77,10 +66,8 @@ describe('Toast', () => {
})
it('should have warning styling when passing warning as type', () => {
props = {
id: '1',
message: 'test message',
...props,
type: 'warning',
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const warningToast = getByTestId('Toast_warning')
Expand All @@ -92,11 +79,8 @@ describe('Toast', () => {
it('after 7 seconds the toast should be closed automatically', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'success',
...props,
duration: 7000,
onClose: jest.fn(),
}
const { getByText } = render(props)
getByText('test message')
Expand All @@ -110,13 +94,10 @@ describe('Toast', () => {
expect(props.onClose).toHaveBeenCalled()
})

it('should stay more than 7 seconds when requiredTimeout is true', async () => {
it('should stay more than 7 seconds when disableTimeout is true', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'success',
onClose: jest.fn(),
...props,
disableTimeout: true,
}
const { getByText } = render(props)
Expand All @@ -131,13 +112,10 @@ describe('Toast', () => {
expect(props.onClose).not.toHaveBeenCalled()
})

it('should not stay more than 7 seconds when requiredTimeout is false', async () => {
it('should not stay more than 7 seconds when disableTimeout is false', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'success',
onClose: jest.fn(),
...props,
disableTimeout: false,
}
const { getByText } = render(props)
Expand All @@ -151,4 +129,23 @@ describe('Toast', () => {
})
expect(props.onClose).toHaveBeenCalled()
})

it('should dismiss when a second toast appears', async () => {
jest.useFakeTimers()
props = {
...props,
disableTimeout: true,
exitNow: true,
}
const { getByText } = render(props)
getByText('test message')
act(() => {
jest.advanceTimersByTime(100)
})
expect(props.onClose).not.toHaveBeenCalled()
act(() => {
jest.advanceTimersByTime(TOAST_ANIMATION_DURATION)
})
expect(props.onClose).toHaveBeenCalled()
})
})
69 changes: 23 additions & 46 deletions app/src/atoms/Toast/__tests__/Toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { act, fireEvent } from '@testing-library/react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../i18n'
import { Toast } from '..'
import { Toast, TOAST_ANIMATION_DURATION } from '..'

const render = (props: React.ComponentProps<typeof Toast>) => {
return renderWithProviders(<Toast {...props} displayType="desktop" />, {
Expand All @@ -29,12 +29,14 @@ describe('Toast', () => {
const { getByText } = render(props)
getByText('test message')
})

it('calls onClose when close button is pressed', () => {
const { getByRole } = render(props)
const closeButton = getByRole('button')
fireEvent.click(closeButton)
expect(props.onClose).toHaveBeenCalled()
})

it('does not render x button if prop is false', () => {
props = {
id: '1',
Expand All @@ -46,27 +48,19 @@ describe('Toast', () => {
const { queryByRole } = render(props)
expect(queryByRole('button')).toBeNull()
})

it('should have success styling when passing success as type', () => {
props = {
id: '1',
message: 'test message',
type: 'success',
closeButton: false,
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const successToast = getByTestId('Toast_success')
expect(successToast).toHaveStyle(`color: #04aa65
background-color: #f3fffa`)
getByLabelText('icon_success')
})

it('should have warning styling when passing warning as type', () => {
props = {
id: '1',
message: 'test message',
...props,
type: 'warning',
closeButton: false,
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const warningToast = getByTestId('Toast_warning')
Expand All @@ -77,11 +71,8 @@ describe('Toast', () => {

it('should have error styling when passing error as type', () => {
props = {
id: '1',
message: 'test message',
...props,
type: 'error',
closeButton: false,
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const errorToast = getByTestId('Toast_error')
Expand All @@ -92,11 +83,8 @@ describe('Toast', () => {

it('should have info styling when passing info as type', () => {
props = {
id: '1',
message: 'test message',
...props,
type: 'info',
closeButton: false,
onClose: jest.fn(),
}
const { getByTestId, getByLabelText } = render(props)
const infoToast = getByTestId('Toast_info')
Expand All @@ -105,15 +93,11 @@ describe('Toast', () => {
getByLabelText('icon_info')
})

it('after 8 seconds the toast should be closed automatically', async () => {
it('should stay more than 7 seconds when disableTimeout is true', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'info',
duration: 8000,
closeButton: false,
onClose: jest.fn(),
...props,
disableTimeout: true,
}
const { getByText } = render(props)
getByText('test message')
Expand All @@ -122,20 +106,16 @@ describe('Toast', () => {
})
expect(props.onClose).not.toHaveBeenCalled()
act(() => {
jest.advanceTimersByTime(9000)
jest.advanceTimersByTime(7000)
})
expect(props.onClose).toHaveBeenCalled()
expect(props.onClose).not.toHaveBeenCalled()
})

it('should stay more than 8 seconds when requiredTimeout is true', async () => {
it('should not stay more than 7 seconds when disableTimeout is false', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'info',
closeButton: false,
onClose: jest.fn(),
disableTimeout: true,
...props,
disableTimeout: false,
}
const { getByText } = render(props)
getByText('test message')
Expand All @@ -144,20 +124,17 @@ describe('Toast', () => {
})
expect(props.onClose).not.toHaveBeenCalled()
act(() => {
jest.advanceTimersByTime(8000)
jest.advanceTimersByTime(9000)
})
expect(props.onClose).not.toHaveBeenCalled()
expect(props.onClose).toHaveBeenCalled()
})

it('should not stay more than 8 seconds when requiredTimeout is false', async () => {
it('should dismiss when a second toast appears', async () => {
jest.useFakeTimers()
props = {
id: '1',
message: 'test message',
type: 'info',
closeButton: false,
onClose: jest.fn(),
disableTimeout: false,
...props,
disableTimeout: true,
exitNow: true,
}
const { getByText } = render(props)
getByText('test message')
Expand All @@ -166,7 +143,7 @@ describe('Toast', () => {
})
expect(props.onClose).not.toHaveBeenCalled()
act(() => {
jest.advanceTimersByTime(9000)
jest.advanceTimersByTime(TOAST_ANIMATION_DURATION)
})
expect(props.onClose).toHaveBeenCalled()
})
Expand Down
Loading

0 comments on commit e88397d

Please sign in to comment.