Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/common/components/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ export interface ToastProps {
/**
* Custom actions to display in the toast (optional). Receives the toast ID
* and dismiss function as parameters. It should be either one or more Buttons or Links.
* If provided, the toast should also have the `isDismissable` prop set to true
* and its duration set to infinite.
* If provided, the toast should also have the `isDismissable` prop set to true.
*/
actions?: ({ id, dismiss }: ToastActionsParams) => ReactNode;
/**
Expand All @@ -94,7 +93,8 @@ export interface ToastProps {
id: ToastId;
/**
* When true, displays a dismiss button allowing users to manually close the toast (optional).
* It must be set to true if the duration of the toast is set to infinite though.
* It must be set to true if the duration of the toast is set to infinite or if actions are
* provided.
* The default is false.
*/
isDismissable?: boolean;
Expand Down
6 changes: 3 additions & 3 deletions src/components/echoes-provider/EchoesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface EchoesProviderProps {
/**
* Maximum number of toast notifications visible simultaneously (optional).
* When this limit is reached, older toasts will be automatically dismissed
* to make room for new ones. The default is 5.
* to make room for new ones. The default is 3.
*/
toastsVisibleNb?: number;
/**
Expand Down Expand Up @@ -88,7 +88,7 @@ export interface EchoesProviderProps {
* ```
*/
export function EchoesProvider(props: PropsWithChildren<EchoesProviderProps>) {
const { children, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 5 } = props;
const { children, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 3 } = props;
const intl = useIntl();
const [portalRef, setPortalRef] = useState<HTMLDivElement | null>(null);

Expand Down Expand Up @@ -136,7 +136,7 @@ EchoesProvider.displayName = 'EchoesProvider';
* It doesn't prevent tests using Modals to work fine.
*/
export function EchoesProviderForTests(props: PropsWithChildren<EchoesProviderProps>) {
const { children, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 5 } = props;
const { children, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 3 } = props;
const intl = useIntl();

return (
Expand Down
10 changes: 5 additions & 5 deletions src/utils/__tests__/toasts-aggregation-ui-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const { resetToastTestState, trackToastId } = createToastTestState({
describe('toast utility - automatic aggregation UI', () => {
afterEach(resetToastTestState);

it('should aggregate repeated toasts without a title when the description and variety match', async () => {
it('should aggregate title-less toasts when description and variety match', async () => {
render(<div />);

trackToastId(toast.success(descriptionOnlyUploadCompleteToast));
Expand All @@ -55,7 +55,7 @@ describe('toast utility - automatic aggregation UI', () => {
expect(screen.getByText(/^2$/)).toBeInTheDocument();
});

it('should treat blank plain-text titles as absent when aggregating repeated toasts', async () => {
it('should treat blank plain-text titles as absent during aggregation', async () => {
render(<div />);

trackToastId(
Expand All @@ -75,7 +75,7 @@ describe('toast utility - automatic aggregation UI', () => {
expect(screen.getByText(/^2$/)).toBeInTheDocument();
});

it('should aggregate repeated toasts with the same plain-text title, description, and variety', async () => {
it('should aggregate repeated toasts when title, description, and variety match', async () => {
render(<div />);

trackToastId(toast.success(uploadCompleteToast));
Expand Down Expand Up @@ -150,7 +150,7 @@ describe('toast utility - automatic aggregation UI', () => {
expect(await screen.findByText('File uploaded')).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(8000);
jest.advanceTimersByTime(3000);
jest.runOnlyPendingTimers();
});

Expand All @@ -174,7 +174,7 @@ describe('toast utility - automatic aggregation UI', () => {
expect(screen.queryByText('Shown 2 times')).not.toBeInTheDocument();
});

it('should keep the aggregation story aggregating when Storybook injects lifecycle action args', async () => {
it('should keep the aggregation story working when Storybook adds lifecycle args', async () => {
jest.useFakeTimers();

const storyElement = AggregatedRepeatedToasts.render!(
Expand Down
107 changes: 99 additions & 8 deletions src/utils/__tests__/toasts-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ describe('toast utility - basic functionality', () => {
title: 'Success title',
description: SUCCESS_MESSAGE,
id: 'custom-id',
duration: ToastDuration.Infinite,
isDismissable: true,
screenReaderPrefix: 'Toast prefix',
actions,
Expand Down Expand Up @@ -164,7 +163,6 @@ describe('toast utility - dismissal and interaction', () => {
toast({
variety: ToastVariety.Success,
description: actionToastMessage,
duration: ToastDuration.Infinite,
isDismissable: true,
actions: ({ dismiss }) => (
<Button
Expand Down Expand Up @@ -192,6 +190,94 @@ describe('toast utility - dismissal and interaction', () => {
expect(onAutoClose).not.toHaveBeenCalled();
});

it('should dismiss a regular toast after the default medium duration', async () => {
jest.useFakeTimers();

const onAutoClose = jest.fn();
const onDismiss = jest.fn();
render(<div />);

trackToastId(
toast({
variety: ToastVariety.Info,
description: TEST_MESSAGE,
onAutoClose,
onDismiss,
}),
);

expect(await screen.findByText(TEST_MESSAGE)).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(4000);
});

expect(screen.getByText(TEST_MESSAGE)).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(1000);
jest.runOnlyPendingTimers();
});

act(() => {
// Sonner finalizes auto-dismiss on the next animation frame.
jest.advanceTimersToNextFrame();
});

await waitFor(() => {
expect(screen.queryByText(TEST_MESSAGE)).not.toBeInTheDocument();
});

expect(onAutoClose).toHaveBeenCalled();
expect(onDismiss).not.toHaveBeenCalled();
});

it('should dismiss an action toast after the default long duration', async () => {
jest.useFakeTimers();

const onAutoClose = jest.fn();
const onDismiss = jest.fn();
render(<div />);

trackToastId(
toast({
variety: ToastVariety.Success,
description: SUCCESS_MESSAGE,
isDismissable: true,
actions: () => <Button>Open report</Button>,
onAutoClose,
onDismiss,
}),
);

expect(await screen.findByText(SUCCESS_MESSAGE)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Open report' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Dismiss toast' })).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(7000);
});

expect(screen.getByText(SUCCESS_MESSAGE)).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(1000);
jest.runOnlyPendingTimers();
});

act(() => {
// Sonner finalizes auto-dismiss on the next animation frame.
jest.advanceTimersToNextFrame();
});

await waitFor(() => {
expect(screen.queryByText(SUCCESS_MESSAGE)).not.toBeInTheDocument();
});

expect(onAutoClose).toHaveBeenCalled();
expect(onDismiss).not.toHaveBeenCalled();
});

it('should dismiss toast after auto-close duration', async () => {
jest.useFakeTimers();

Expand All @@ -215,16 +301,21 @@ describe('toast utility - dismissal and interaction', () => {
expect(await screen.findByText(WARNING_DESCRIPTION)).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(4000);
jest.advanceTimersByTime(500);
});
expect(await screen.findByText(WARNING_DESCRIPTION)).toBeInTheDocument();

// It should auto-close after 8 seconds, we are at 6 seconds now
// It should auto-close after 3 seconds, we are at 2.5 seconds now.
act(() => {
jest.advanceTimersByTime(4000);
jest.advanceTimersByTime(500);
jest.runOnlyPendingTimers();
});

act(() => {
// Sonner finalizes auto-dismiss on the next animation frame.
jest.advanceTimersToNextFrame();
});

await waitFor(() => {
expect(screen.queryByText(WARNING_DESCRIPTION)).not.toBeInTheDocument();
});
Expand Down Expand Up @@ -262,7 +353,7 @@ describe('toast utility - stable id updates', () => {
expect(screen.queryByText('Synchronizing repository settings...')).not.toBeInTheDocument();
});

it('should stop automatic aggregation immediately when a returned aggregated id is later reused explicitly', async () => {
it('should stop automatic aggregation after explicit reuse of an aggregated id', async () => {
render(<div />);

const repeatedToastId = trackToastId(
Expand Down Expand Up @@ -308,7 +399,7 @@ describe('toast utility - stable id updates', () => {
expect(screen.queryByText(/^Shown \d+ times$/)).not.toBeInTheDocument();
});

it('should not reuse an auto-generated repeated-toast id after the previous toast is dismissed', async () => {
it('should create a new generated id after the repeated toast is dismissed', async () => {
render(<div />);

const firstToastId = trackToastId(
Expand Down Expand Up @@ -409,7 +500,7 @@ describe('toast utility - automatic aggregation opt-outs', () => {
});

act(() => {
jest.advanceTimersByTime(8000);
jest.advanceTimersByTime(3000);
jest.runOnlyPendingTimers();
});

Expand Down
Loading
Loading