diff --git a/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.test.tsx b/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.test.tsx new file mode 100644 index 0000000000..2ac505668b --- /dev/null +++ b/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.test.tsx @@ -0,0 +1,128 @@ +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { ConfirmationDialog } from './confirmation-dialog'; + +jest.mock('i18next', () => ({ + t: (key: string) => key, +})); + +describe('ConfirmationDialog', () => { + it('renders headerLeading beside the title and description', () => { + render( + !} + />, + ); + + expect(screen.getByTestId('leading-icon')).toBeInTheDocument(); + expect( + screen.getByRole('heading', { name: 'Launch Campaign?' }), + ).toBeInTheDocument(); + expect(screen.getByText('Please verify settings.')).toBeInTheDocument(); + }); + + it('invokes onConfirm when the confirm button is clicked', async () => { + const user = userEvent.setup(); + const onConfirm = jest.fn(); + + render( + , + ); + + await user.click(screen.getByRole('button', { name: 'Go' })); + expect(onConfirm).toHaveBeenCalledTimes(1); + }); + + it('renders a rich-text description node', () => { + render( + + You will lose $8,200 in + savings. + + } + onConfirm={jest.fn()} + onCancel={jest.fn()} + />, + ); + + expect(screen.getByTestId('highlight')).toHaveTextContent('$8,200'); + }); + + it('renders the confirm button with the destructive variant when requested', () => { + render( + , + ); + + expect(screen.getByRole('button', { name: 'Delete' })).toHaveClass( + 'bg-destructive', + ); + }); + + it('renders the confirm button with the default variant when no variant is given', () => { + render( + , + ); + + expect(screen.getByRole('button', { name: 'Go' })).not.toHaveClass( + 'bg-destructive', + ); + }); + + it('invokes onCancel when the cancel button is clicked', async () => { + const user = userEvent.setup(); + const onCancel = jest.fn(); + + render( + , + ); + + await user.click(screen.getByRole('button', { name: 'Cancel' })); + expect(onCancel).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.tsx b/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.tsx index 859925a60f..1b8aa0668e 100644 --- a/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.tsx +++ b/packages/ui-components/src/components/confirmation-dialog/confirmation-dialog.tsx @@ -1,5 +1,7 @@ import { t } from 'i18next'; -import { Button } from '../../ui/button'; +import type { ReactNode } from 'react'; +import { cn } from '../../lib/cn'; +import { Button, ButtonProps } from '../../ui/button'; import { Dialog, DialogContent, @@ -17,13 +19,16 @@ type ConfirmationDialogProps = { className?: string; titleClassName?: string; descriptionClassName?: string; + /** Renders beside the title and description (e.g. warning icon) for modal layouts that need a leading visual. */ + headerLeading?: ReactNode; children?: React.ReactNode; }; export type ConfirmationDialogContent = { title: string; - description: string; + description: ReactNode; confirmButtonText?: string; + confirmButtonVariant?: ButtonProps['variant']; cancelButtonText?: string; }; @@ -33,21 +38,43 @@ const ConfirmationDialog = ({ title, description, confirmButtonText, + confirmButtonVariant, cancelButtonText, onConfirm, onCancel, titleClassName, descriptionClassName, + headerLeading, + className, children, }: ConfirmationDialogProps & ConfirmationDialogContent) => { return ( - - - {title} - - {description} - + + + {headerLeading != null ? ( +
+
{headerLeading}
+
+ {title} + + {description} + +
+
+ ) : ( + <> + {title} + + {description} + + + )}
{children} @@ -56,7 +83,7 @@ const ConfirmationDialog = ({ {cancelButtonText ? cancelButtonText : t('Cancel')} )} -