From 76b4c812cb2e7692276efd4de4736b3c35dc8a5f Mon Sep 17 00:00:00 2001 From: Sem Bauke Date: Fri, 29 Mar 2024 13:50:33 +0100 Subject: [PATCH] feat: migrate setting modals (#54112) Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Co-authored-by: ahmad abdolsaheb --- .../SolutionViewer/exam-results-modal.tsx | 53 +++++++------- .../SolutionViewer/project-modal.tsx | 22 ++---- client/src/components/layouts/global.css | 11 +++ .../profile/components/time-line.tsx | 19 ++--- .../src/components/settings/delete-modal.tsx | 25 ++----- .../src/components/settings/reset-modal.tsx | 25 ++----- client/src/components/signout-modal/index.tsx | 20 ++---- .../staging-warning-modal/index.tsx | 25 ++----- .../staging-warning-modal.test.tsx | 31 ++++++-- .../Challenges/components/reset-modal.css | 24 ------- .../Challenges/components/reset-modal.tsx | 28 ++------ e2e/delete-modal.spec.ts | 48 +++++++------ e2e/lower-jaw.spec.ts | 6 +- e2e/reset-modal.spec.ts | 20 +++--- e2e/signout-modal.spec.ts | 30 ++++---- e2e/solution-display-widget.spec.ts | 71 ------------------- e2e/solution-viewer.spec.ts | 65 +++++++++++++---- tools/ui-components/src/modal/modal.tsx | 23 +++--- tools/ui-components/src/modal/types.ts | 5 ++ 19 files changed, 223 insertions(+), 328 deletions(-) delete mode 100644 client/src/templates/Challenges/components/reset-modal.css delete mode 100644 e2e/solution-display-widget.spec.ts diff --git a/client/src/components/SolutionViewer/exam-results-modal.tsx b/client/src/components/SolutionViewer/exam-results-modal.tsx index a3c098a89f2..fe8a5f23cfe 100644 --- a/client/src/components/SolutionViewer/exam-results-modal.tsx +++ b/client/src/components/SolutionViewer/exam-results-modal.tsx @@ -1,8 +1,7 @@ -import { Modal } from '@freecodecamp/react-bootstrap'; import { connect } from 'react-redux'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Row, Button } from '@freecodecamp/ui'; +import { Row, Button, Modal } from '@freecodecamp/ui'; import type { GeneratedExamResults } from '../../redux/prop-types'; import { closeModal } from '../../templates/Challenges/redux/actions'; @@ -51,36 +50,36 @@ const ExamResultsModal = ({ return ( { + onClose={() => { closeModal('examResults'); }} - show={isOpen} - size='lg' + open={isOpen} + size='large' > - - - {t('settings.labels.results-for', { projectTitle })} - + + {t('settings.labels.results-for', { projectTitle })} - + - - {t('learn.exam.number-of-questions', { - n: numberOfQuestionsInExam - })} - {' '} - - - {t('learn.exam.correct-answers', { n: numberOfCorrectAnswers })} - {' '} - - {t('learn.exam.percent-correct', { n: percentCorrect })} - {' '} - - {t('learn.exam.time', { t: formatSecondsToTime(examTimeInSeconds) })} - +
+ + {t('learn.exam.number-of-questions', { + n: numberOfQuestionsInExam + })} + {' '} + + + {t('learn.exam.correct-answers', { n: numberOfCorrectAnswers })} + {' '} + + {t('learn.exam.percent-correct', { n: percentCorrect })} + {' '} + + {t('learn.exam.time', { + t: formatSecondsToTime(examTimeInSeconds) + })} + +
diff --git a/client/src/components/SolutionViewer/project-modal.tsx b/client/src/components/SolutionViewer/project-modal.tsx index 865cc406f44..ab28be2f4e7 100644 --- a/client/src/components/SolutionViewer/project-modal.tsx +++ b/client/src/components/SolutionViewer/project-modal.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Modal } from '@freecodecamp/react-bootstrap'; -import { Button } from '@freecodecamp/ui'; +import { Button, Modal } from '@freecodecamp/ui'; import { CompletedChallenge } from '../../redux/prop-types'; import SolutionViewer from './solution-viewer'; @@ -23,23 +22,14 @@ const ProjectModal = ({ }: ProjectModalProps): JSX.Element => { const { t } = useTranslation(); return ( - - - - {t('settings.labels.solution-for', { projectTitle })} - + + + {t('settings.labels.solution-for', { projectTitle })} - + - + - - - ); diff --git a/client/src/components/settings/reset-modal.tsx b/client/src/components/settings/reset-modal.tsx index d0d8848b089..431680db24e 100644 --- a/client/src/components/settings/reset-modal.tsx +++ b/client/src/components/settings/reset-modal.tsx @@ -1,7 +1,6 @@ -import { Modal } from '@freecodecamp/react-bootstrap'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Button } from '@freecodecamp/ui'; +import { Button, Modal } from '@freecodecamp/ui'; import { Spacer } from '../helpers'; @@ -16,24 +15,15 @@ function ResetModal(props: ResetModalProps): JSX.Element { const { show, onHide } = props; return ( - - - - {t('settings.danger.reset-heading')} - + + + {t('settings.danger.reset-heading')}

{t('settings.danger.reset-p1')}

{t('settings.danger.reset-p2')}

-
+
+ -
- -
); diff --git a/client/src/components/signout-modal/index.tsx b/client/src/components/signout-modal/index.tsx index 014352539ba..e0391204cf6 100644 --- a/client/src/components/signout-modal/index.tsx +++ b/client/src/components/signout-modal/index.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { Modal } from '@freecodecamp/react-bootstrap'; import { bindActionCreators, Dispatch, AnyAction } from 'redux'; import { createSelector } from 'reselect'; import { connect } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import { Button } from '@freecodecamp/ui'; +import { Button, Modal } from '@freecodecamp/ui'; import { Spacer } from '../helpers'; import { hardGoTo as navigate, closeSignoutModal } from '../../redux/actions'; @@ -47,20 +46,9 @@ function SignoutModal(props: SignoutModalProps): JSX.Element { }; return ( - - - - {t('signout.heading')} - + + + {t('signout.heading')}

diff --git a/client/src/components/staging-warning-modal/index.tsx b/client/src/components/staging-warning-modal/index.tsx index 26c5eae3003..3a0fd5a2430 100644 --- a/client/src/components/staging-warning-modal/index.tsx +++ b/client/src/components/staging-warning-modal/index.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; -import { Modal } from '@freecodecamp/react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; -import { Button } from '@freecodecamp/ui'; +import { Button, Modal } from '@freecodecamp/ui'; import store from 'store'; import { Spacer } from '../helpers'; @@ -17,23 +16,11 @@ function StagingWarningModal(): JSX.Element { setShow(false); }; return ( - - - - - {t('staging-warning.heading')} - - + + + + {t('staging-warning.heading')} +

{t('staging-warning.p1')}

diff --git a/client/src/components/staging-warning-modal/staging-warning-modal.test.tsx b/client/src/components/staging-warning-modal/staging-warning-modal.test.tsx index 8c816e7e9d8..8d44ab00ea9 100644 --- a/client/src/components/staging-warning-modal/staging-warning-modal.test.tsx +++ b/client/src/components/staging-warning-modal/staging-warning-modal.test.tsx @@ -2,23 +2,40 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import store from 'store'; import StagingWarningModal from '.'; - describe('StagingWarningModal', () => { + beforeAll(() => { + // The Modal component uses `ResizeObserver` under the hood. + // However, this property is not available in JSDOM, so we need to manually add it to the window object. + // Ref: https://github.com/jsdom/jsdom/issues/3368 + Object.defineProperty(window, 'ResizeObserver', { + writable: true, + value: jest.fn(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() + })) + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('renders the modal successfully', () => { render(); - expect(screen.getByTestId('staging-warning-modal')).toBeInTheDocument(); - expect(screen.getByTestId('staging-warning-modal')).toHaveClass('in'); + expect(screen.getByRole('dialog')).toBeInTheDocument(); }); it('closes the modal when clicking the close button', () => { render(); fireEvent.click(screen.getByText('Close')); - expect(screen.getByTestId('staging-warning-modal')).not.toHaveClass('in'); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); it('displays the correct modal content', () => { render(); - const modalContent = screen.getByTestId('staging-warning-modal'); + const modalContent = screen.getByRole('dialog'); expect(modalContent).toHaveTextContent('staging-warning.heading'); expect(modalContent).toHaveTextContent('staging-warning.p1'); expect(modalContent).toHaveTextContent('staging-warning.p2'); @@ -27,8 +44,10 @@ describe('StagingWarningModal', () => { it('accepts Warning, stores acceptance key in local storage, and closes the modal', () => { render(); + fireEvent.click(screen.getByTestId('accepts-warning')); expect(store.get('acceptedStagingWarning')).toBe(true); - expect(screen.queryByTestId('staging-warning-modal')).not.toHaveClass('in'); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); }); diff --git a/client/src/templates/Challenges/components/reset-modal.css b/client/src/templates/Challenges/components/reset-modal.css deleted file mode 100644 index c5376551ad4..00000000000 --- a/client/src/templates/Challenges/components/reset-modal.css +++ /dev/null @@ -1,24 +0,0 @@ -.reset-modal p { - color: var(--danger-color); -} - -.reset-modal-header { - color: var(--danger-color); - background-color: var(--danger-background); -} - -.reset-modal-header h4 { - color: var(--danger-color); -} - -.reset-modal-header .close { - color: var(--danger-color); - font-size: 28px; - text-shadow: none; -} - -@media screen and (max-width: 767px) { - .reset-modal .btn-lg { - font-size: 16px; - } -} diff --git a/client/src/templates/Challenges/components/reset-modal.tsx b/client/src/templates/Challenges/components/reset-modal.tsx index 5d7b29d3c12..353e6b8e6e4 100644 --- a/client/src/templates/Challenges/components/reset-modal.tsx +++ b/client/src/templates/Challenges/components/reset-modal.tsx @@ -1,28 +1,20 @@ -// Package Utilities -import { Modal } from '@freecodecamp/react-bootstrap'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { createSelector } from 'reselect'; -import { Button } from '@freecodecamp/ui'; +import { Button, Modal } from '@freecodecamp/ui'; -// Local Utilities import { closeModal, resetChallenge } from '../redux/actions'; import { isResetModalOpenSelector } from '../redux/selectors'; import callGA from '../../../analytics/call-ga'; -// Styles -import './reset-modal.css'; - -// Types interface ResetModalProps { close: () => void; isOpen: boolean; reset: () => void; } -// Redux Setup const mapStateToProps = createSelector( isResetModalOpenSelector, (isOpen: boolean) => ({ @@ -43,25 +35,17 @@ function withActions(...fns: Array<() => void>) { return () => fns.forEach(fn => fn()); } -// Component function ResetModal({ reset, close, isOpen }: ResetModalProps): JSX.Element { const { t } = useTranslation(); if (isOpen) { callGA({ event: 'pageview', pagePath: '/reset-modal' }); } return ( - - - {t('learn.reset')} + + + {t('learn.reset')} - +

{t('learn.reset-warn')}

@@ -69,7 +53,7 @@ function ResetModal({ reset, close, isOpen }: ResetModalProps): JSX.Element {

- +