From 7f353e33cecf8098e48e7da715d971f296176828 Mon Sep 17 00:00:00 2001 From: Tran Manh Date: Tue, 25 Jul 2023 11:31:08 +0700 Subject: [PATCH] [#22] Create a confirm dialog --- src/components/Dialog/Confirm/index.test.tsx | 38 +++++++++++++ src/components/Dialog/Confirm/index.tsx | 60 ++++++++++++++++++++ src/components/Dialog/index.test.tsx | 25 ++++++++ src/components/Dialog/index.tsx | 24 ++++++++ src/components/ElevatedButton/index.tsx | 16 +++++- src/screens/Question/index.test.tsx | 49 +++++++++++++++- src/screens/Question/index.tsx | 15 +++++ 7 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 src/components/Dialog/Confirm/index.test.tsx create mode 100644 src/components/Dialog/Confirm/index.tsx create mode 100644 src/components/Dialog/index.test.tsx create mode 100644 src/components/Dialog/index.tsx diff --git a/src/components/Dialog/Confirm/index.test.tsx b/src/components/Dialog/Confirm/index.test.tsx new file mode 100644 index 0000000..fe94541 --- /dev/null +++ b/src/components/Dialog/Confirm/index.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { render, screen } from '@testing-library/react'; + +import ConfirmDialog, { confirmDialogDataTestIds } from '.'; +import { dialogDataTestIds } from '..'; + +describe('ConfirmDialog', () => { + const emptyCallback = () => { + // Do nothing + }; + + describe('given the confirm dialog is opened', () => { + it('renders a confirm dialog', () => { + render( + + Test Content + + ); + + const confirmDialog = screen.getByTestId(confirmDialogDataTestIds.base); + + expect(confirmDialog).toBeVisible(); + }); + }); + + describe('given the dialog is NOT opened', () => { + it('does NOT render a dialog', () => { + render( + + Test Content + + ); + + expect(screen.queryByTestId(dialogDataTestIds.dialog)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Dialog/Confirm/index.tsx b/src/components/Dialog/Confirm/index.tsx new file mode 100644 index 0000000..785cc8e --- /dev/null +++ b/src/components/Dialog/Confirm/index.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import ElevatedButton from 'components/ElevatedButton'; + +import Dialog from '..'; + +export const confirmDialogDataTestIds = { + base: 'confirm-dialog__base', + positiveButton: 'confirm-dialog__positive-button', + negativeButton: 'confirm-dialog__negative-button', +}; + +interface ConfirmDialogProps { + title: string; + children: React.ReactNode; + open: boolean; + onClose: () => void; + onConfirm: () => void; +} +const ConfirmDialog = ({ open, onClose, title, children, onConfirm }: ConfirmDialogProps): JSX.Element => { + if (!open) { + return <>; + } + + return ( +
+ +

{title}

+
{children}
+
+
+ { + onClose(); + onConfirm(); + }} + data-test-id={confirmDialogDataTestIds.positiveButton} + > + Yes + +
+
+ onClose()} + data-test-id={confirmDialogDataTestIds.negativeButton} + > + Cancel + +
+
+
+
+ ); +}; + +export default ConfirmDialog; diff --git a/src/components/Dialog/index.test.tsx b/src/components/Dialog/index.test.tsx new file mode 100644 index 0000000..38a2a48 --- /dev/null +++ b/src/components/Dialog/index.test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { render, screen } from '@testing-library/react'; + +import Dialog, { dialogDataTestIds } from '.'; + +describe('Dialog', () => { + describe('given the dialog is opened', () => { + it('renders a dialog', () => { + render(Test Content); + + const dialog = screen.getByTestId(dialogDataTestIds.dialog); + + expect(dialog).toBeVisible(); + }); + }); + + describe('given the dialog is NOT opened', () => { + it('does NOT render a dialog', () => { + render(Test Content); + + expect(screen.queryByTestId(dialogDataTestIds.dialog)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx new file mode 100644 index 0000000..564021d --- /dev/null +++ b/src/components/Dialog/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export const dialogDataTestIds = { + dialog: 'dialog__base', +}; +interface DialogProps { + children: React.ReactNode; + open: boolean; +} +const Dialog = (props: DialogProps): JSX.Element => { + const { open } = props; + if (!open) { + return <>; + } + return ( +
+
+
{props.children}
+
+
+ ); +}; + +export default Dialog; diff --git a/src/components/ElevatedButton/index.tsx b/src/components/ElevatedButton/index.tsx index 0212d04..30fdcb4 100644 --- a/src/components/ElevatedButton/index.tsx +++ b/src/components/ElevatedButton/index.tsx @@ -5,14 +5,24 @@ import classNames from 'classnames'; interface ElevatedButtonProps extends React.ButtonHTMLAttributes { children: React.ReactNode; isFullWidth: boolean; + className?: string; } -const ElevatedButton = ({ children, isFullWidth, ...rest }: ElevatedButtonProps): JSX.Element => { +const ElevatedButton = ({ + children, + isFullWidth, + className = 'bg-white text-black-chinese', + ...attributes +}: ElevatedButtonProps): JSX.Element => { const DEFAULT_CLASS_NAMES = - 'bg-white text-black-chinese font-bold text-regular tracking-survey-tight rounded-[10px] focus:outline-none focus:shadow-outline h-14'; + 'font-bold text-regular tracking-survey-tight rounded-[10px] focus:outline-none focus:shadow-outline h-14'; return ( - ); diff --git a/src/screens/Question/index.test.tsx b/src/screens/Question/index.test.tsx index 0c36e22..6c28fb3 100644 --- a/src/screens/Question/index.test.tsx +++ b/src/screens/Question/index.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { answerDataTestIds } from 'components/Answer'; +import { confirmDialogDataTestIds } from 'components/Dialog/Confirm'; import { useAppDispatch, useAppSelector } from 'hooks'; import { paths } from 'routes'; import { SurveyState } from 'store/reducers/Survey'; @@ -88,7 +89,7 @@ describe('QuestionScreen', () => { }); describe('given the close button is clicked', () => { - it('navigates back to the Home screen', () => { + it('shows the confirm dialog', () => { render(); const closeButton = screen.getByTestId(questionScreenTestIds.closeButton); @@ -97,8 +98,50 @@ describe('QuestionScreen', () => { closeButton.click(); }); - expect(mockDispatch).toHaveBeenCalledWith({ type: 'survey/resetState' }); - expect(mockUseNavigate).toHaveBeenCalledWith(paths.root, { replace: true }); + expect(screen.getByTestId(confirmDialogDataTestIds.base)).toBeVisible(); + }); + }); + + describe('given the dialog is opened', () => { + describe('given the Yes button is clicked', () => { + it('navigates back to the Home screen', () => { + render(); + + const closeButton = screen.getByTestId(questionScreenTestIds.closeButton); + + act(() => { + closeButton.click(); + }); + + const dialogPositiveButton = screen.getByTestId(confirmDialogDataTestIds.positiveButton); + + act(() => { + dialogPositiveButton.click(); + }); + + expect(mockDispatch).toHaveBeenCalledWith({ type: 'survey/resetState' }); + expect(mockUseNavigate).toHaveBeenCalledWith(paths.root, { replace: true }); + }); + }); + + describe('given the Cancel button is clicked', () => { + it('closes the dialog', () => { + render(); + + const closeButton = screen.getByTestId(questionScreenTestIds.closeButton); + + act(() => { + closeButton.click(); + }); + + const dialogNegativeButton = screen.getByTestId(confirmDialogDataTestIds.negativeButton); + + act(() => { + dialogNegativeButton.click(); + }); + + expect(screen.queryByTestId(confirmDialogDataTestIds.base)).not.toBeInTheDocument(); + }); }); }); diff --git a/src/screens/Question/index.tsx b/src/screens/Question/index.tsx index 2a31e3c..3b4917e 100644 --- a/src/screens/Question/index.tsx +++ b/src/screens/Question/index.tsx @@ -5,6 +5,7 @@ import { ToastContainer, toast } from 'react-toastify'; import { ReactComponent as ArrowRight } from 'assets/images/icons/arrow-right.svg'; import { ReactComponent as CloseButton } from 'assets/images/icons/close-btn.svg'; import Answer from 'components/Answer'; +import ConfirmDialog from 'components/Dialog/Confirm'; import ElevatedButton from 'components/ElevatedButton'; import LoadingDialog from 'components/LoadingDialog'; import MainView from 'components/MainView'; @@ -30,6 +31,8 @@ const QuestionScreen = (): JSX.Element => { const [currentQuestion, setCurrentQuestion] = useState(survey?.questions?.at(0)); const [questionIndex, setQuestionIndex] = useState(0); + const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); + const handleAnswerChanged = (answers: AnswerRequest[]) => { dispatch( surveyAction.fillAnswers({ @@ -72,6 +75,10 @@ const QuestionScreen = (): JSX.Element => { }; const handleOnClose = () => { + setIsConfirmDialogOpen(true); + }; + + const handleOnConfirm = () => { dispatch(surveyAction.resetState()); navigate(paths.root, { replace: true }); }; @@ -93,6 +100,14 @@ const QuestionScreen = (): JSX.Element => { return (
+ setIsConfirmDialogOpen(false)} + onConfirm={handleOnConfirm} + > + Are you sure you want to quit the survey? +