From 27f6b4742d83a17bcff7ab2259bdea0034598379 Mon Sep 17 00:00:00 2001 From: YulichkaR Date: Thu, 25 Jul 2024 17:21:58 +0300 Subject: [PATCH 1/4] Edit container for Change Password and create a modal popup for Password change --- .../translations/en/edit-profile.json | 4 +- .../translations/uk/edit-profile.json | 2 + .../PasswordSecurityTab.styles.ts | 31 +-- .../PasswordSecurityTab.tsx | 181 ++++++++++-------- .../PasswordSecurityItem.styles.ts | 20 ++ .../PasswordSecurityItem.tsx | 44 +++++ .../PasswordSecurityTab.spec.jsx | 44 ++++- 7 files changed, 227 insertions(+), 99 deletions(-) create mode 100644 src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts create mode 100644 src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx diff --git a/src/constants/translations/en/edit-profile.json b/src/constants/translations/en/edit-profile.json index f1d42b1fa..543bceee7 100644 --- a/src/constants/translations/en/edit-profile.json +++ b/src/constants/translations/en/edit-profile.json @@ -72,13 +72,15 @@ "passwordSecurityTab": { "tabTitle": "Password & Security", "title": "Password & Security", + "subTitle": "You can change your password using the current one.", "description": "Change password and security settings", "changePassword": "Change password", + "changePasswordModalDescription": "Reset your current password to new one.", "changePasswordConfirm": "Are you sure you want to change the password?", "currentPassword": "Current password", "newPassword": "New password", "retypePassword": "Re-type new password", - "savePassword": "Save new password", + "savePassword": "Save the password", "deactivateAccount": "Deactivate account", "deactivateBtn": "Deactivate", "deactivateTitle": "Deactivate your account?", diff --git a/src/constants/translations/uk/edit-profile.json b/src/constants/translations/uk/edit-profile.json index 15775a79c..d1d724a57 100644 --- a/src/constants/translations/uk/edit-profile.json +++ b/src/constants/translations/uk/edit-profile.json @@ -72,8 +72,10 @@ "passwordSecurityTab": { "tabTitle": "Пароль і Безпека", "title": "Пароль і Безпека", + "subTitle": "Ви можете змінити свій пароль, використовуючи поточний.", "description": "Зміна паролю та налаштуваннь безпеки", "changePassword": "Змінити пароль", + "changePasswordModalDescription": "Змінити поточний пароль на новий.", "changePasswordConfirm": "Ви впевнені, що бажаєте змінити пароль?", "currentPassword": "Поточний пароль", "newPassword": "Новий пароль", diff --git a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts index 768375f7d..82a119a53 100644 --- a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts +++ b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts @@ -4,14 +4,12 @@ import { rootContainer } from '~/containers/edit-profile/common.styles' export const styles = { container: rootContainer, titleAndDescription: { - title: { - typography: TypographyVariantEnum.H6 - }, - description: { - typography: TypographyVariantEnum.Body2, - color: 'primary.500', - mb: '30px' - } + typography: TypographyVariantEnum.H5 + }, + description: { + typography: TypographyVariantEnum.Subtitle1, + color: 'primary.500', + mb: '30px' }, subtitle: { typography: TypographyVariantEnum.Body1, @@ -20,13 +18,9 @@ export const styles = { form: { display: 'flex', flexDirection: 'column', gap: '8px' }, passwordButtonsContainer: { - m: '10px 0 20px', - display: 'grid', - gridTemplateColumns: { - sm: 'repeat(5, minmax(0, 1fr))', - md: 'repeat(7, minmax(0, 1fr))', - lg: 'repeat(10, minmax(0, 1fr))' - }, + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'flex-end', gap: '10px' }, @@ -39,5 +33,12 @@ export const styles = { deactivateButton: { mt: '20px', backgroundColor: 'error.700' + }, + modalContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + position: 'relative' } } diff --git a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx index 8ba263a83..5c2a4e29a 100644 --- a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx +++ b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx @@ -1,14 +1,13 @@ -import { useCallback, FC } from 'react' +import { useCallback, useState, FC } from 'react' import { useTranslation } from 'react-i18next' import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' -import Divider from '@mui/material/Divider' +import Modal from '@mui/material/Modal' import Loader from '~/components/loader/Loader' import AppButton from '~/components/app-button/AppButton' import AppTextField from '~/components/app-text-field/AppTextField' -import TitleWithDescription from '~/components/title-with-description/TitleWithDescription' import { snackbarVariants } from '~/constants' import useForm from '~/hooks/use-form' @@ -22,6 +21,7 @@ import { AuthService } from '~/services/auth-service' import { openAlert } from '~/redux/features/snackbarSlice' import { styles } from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles' +import PasswordSecurityItem from './password-security-item/PasswordSecurityItem' import { ButtonVariantEnum, @@ -45,6 +45,8 @@ const PasswordSecurityTab: FC = () => { const { neededAction, checkStatusChange } = useChangeUserStatus() + const [isModalOpen, setIsModalOpen] = useState(false) + const handleSubmitChangePassword = async () => { const confirmed = await checkConfirmation({ message: t( @@ -69,6 +71,7 @@ const PasswordSecurityTab: FC = () => { message: 'editProfilePage.profile.successMessage' }) ) + setIsModalOpen(false) } const changePassword = useCallback( @@ -108,6 +111,14 @@ const PasswordSecurityTab: FC = () => { validations: validations }) + const openChangePasswordModal = () => { + setIsModalOpen(true) + } + + const closeChangePasswordModal = () => { + setIsModalOpen(false) + } + const handleChangeStatusClick = () => { void checkStatusChange( `editProfilePage.profile.passwordSecurityTab.${neededAction}Title`, @@ -132,6 +143,7 @@ const PasswordSecurityTab: FC = () => { const onDiscard = () => { resetData() resetErrors() + closeChangePasswordModal() } const saveButtonContent = loading ? ( @@ -144,84 +156,99 @@ const PasswordSecurityTab: FC = () => { isVisible ? InputEnum.Text : InputEnum.Password return ( - - + - - - {t('editProfilePage.profile.passwordSecurityTab.changePassword')} - - - - - - - - - {saveButtonContent} - - - {t('common.discard')} - + + + + + {t('editProfilePage.profile.passwordSecurityTab.changePassword')} + + + {t( + 'editProfilePage.profile.passwordSecurityTab.changePasswordModalDescription' + )} + + + + + + + + + + {t('common.cancel')} + + + {saveButtonContent} + + + + - - - {t( - `editProfilePage.profile.passwordSecurityTab.${neededAction}Account` - )} - - + + + {t( + `editProfilePage.profile.passwordSecurityTab.${neededAction}Account` + )} + ) } diff --git a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts new file mode 100644 index 000000000..4b5dd9f75 --- /dev/null +++ b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts @@ -0,0 +1,20 @@ +import { TypographyVariantEnum } from '~/types' + +export const styles = { + container: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + m: '20px 0' + }, + title: { + typography: TypographyVariantEnum.Button + }, + description: { + typography: TypographyVariantEnum.Body2, + color: 'primary.500' + }, + titlesAndButtonContainer: { + padding: '20px 0' + } +} diff --git a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx new file mode 100644 index 000000000..4ab90fec6 --- /dev/null +++ b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx @@ -0,0 +1,44 @@ +import { SxProps } from '@mui/material' +import Box from '@mui/material/Box' +import Typography from '@mui/material/Typography' +import { Theme } from '@emotion/react' + +import AppButton from '~/components/app-button/AppButton' +import AppCard from '~/components/app-card/AppCard' +import { styles } from '~/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles' + +import { ButtonVariantEnum, SizeEnum } from '~/types' + +interface PasswordSecurityItemProps { + title: string + description: string + buttonText: string + onClick: () => void + sx?: SxProps +} + +const PasswordSecurityItem = ({ + title, + description, + buttonText, + onClick, + sx +}: PasswordSecurityItemProps) => ( + + + {title} + {description} + + + + {buttonText} + + +) + +export default PasswordSecurityItem diff --git a/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx b/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx index 832d4e5e0..71aa31c1a 100644 --- a/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx +++ b/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx @@ -31,6 +31,14 @@ const changeInputValue = (label, value) => { fireEvent.change(label, { target: { value } }) } +const setupChangePasswordButton = () => { + const changePasswordButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.changePassword/i + ) + return { + changePasswordButton + } +} const setupChangePasswordFields = () => { const currentPasswordInput = screen.getByLabelText( /editProfilePage.profile.passwordSecurityTab.currentPassword/i @@ -76,9 +84,13 @@ describe('PasswordSecurityTab', () => { it('should save data after positive response', async () => { mockAxiosClient - .onPatch(`${URLs.auth.changePassword}/${userDataMock}`) + .onPatch(`${URLs.auth.changePassword}/${userDataMock._id}`) .reply(200) + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + const { currentPasswordInput, passwordInput, @@ -120,6 +132,9 @@ describe('PasswordSecurityTab', () => { .reply(400, { message: 'new password cannot be the same as the current one' }) + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) const { currentPasswordInput, @@ -145,6 +160,9 @@ describe('PasswordSecurityTab', () => { }) it('should do not save empty fields', async () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) const { currentPasswordInput, passwordInput, @@ -172,6 +190,9 @@ describe('PasswordSecurityTab', () => { }) it('should show visibility icon', async () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) const visibilityOffIcons = screen.getAllByTestId('VisibilityOffIcon') const visibilityOffIcon = visibilityOffIcons[0] fireEvent.click(visibilityOffIcon) @@ -189,25 +210,36 @@ describe('PasswordSecurityTab', () => { 'editProfilePage.profile.passwordSecurityTab.title' ) const description = screen.getByText( - 'editProfilePage.profile.passwordSecurityTab.description' + 'editProfilePage.profile.passwordSecurityTab.subTitle' ) expect(title).toBeInTheDocument() expect(description).toBeInTheDocument() }) it('resets form when discard button is clicked', () => { - const { currentPasswordInput } = setupChangePasswordFields() + const { changePasswordButton } = setupChangePasswordButton() - const discardButtonText = screen.getByText('common.discard') + fireEvent.click(changePasswordButton) - changeInputValue(currentPasswordInput, 'oldPassword') + const { passwordInput } = setupChangePasswordFields() + const discardButtonText = screen.getByText('common.cancel') + + changeInputValue(passwordInput, 'oldPassword') fireEvent.click(discardButtonText) + fireEvent.click(changePasswordButton) - expect(currentPasswordInput).toHaveValue('') + const cleanedPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + + expect(cleanedPasswordInput).toHaveValue('') }) it('updates state when form fields are changed', () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) const { currentPasswordInput } = setupChangePasswordFields() changeInputValue(currentPasswordInput, 'oldPassword') From 89d2bee8b13e419f3a38e54e5189dc203a5b660f Mon Sep 17 00:00:00 2001 From: YulichkaR Date: Fri, 26 Jul 2024 14:36:46 +0300 Subject: [PATCH 2/4] Separated change password modal and used custom hook for processing modal --- .../PasswordSecurityTab.styles.ts | 2 +- .../PasswordSecurityTab.tsx | 220 +----------------- .../ChangePasswordModal.tsx | 211 +++++++++++++++++ .../PasswordSecurityItem.styles.ts | 2 +- .../PasswordSecurityItem.tsx | 2 +- 5 files changed, 224 insertions(+), 213 deletions(-) create mode 100644 src/containers/edit-profile/password-security-tab/change-password-modal/ChangePasswordModal.tsx diff --git a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts index 82a119a53..17725fb65 100644 --- a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts +++ b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles.ts @@ -38,7 +38,7 @@ export const styles = { display: 'flex', alignItems: 'center', justifyContent: 'center', - height: '100vh', + width: '700px', position: 'relative' } } diff --git a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx index 5c2a4e29a..ca8b2d16c 100644 --- a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx +++ b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx @@ -1,124 +1,26 @@ -import { useCallback, useState, FC } from 'react' +import { FC } from 'react' import { useTranslation } from 'react-i18next' - import Box from '@mui/material/Box' -import Typography from '@mui/material/Typography' -import Modal from '@mui/material/Modal' - -import Loader from '~/components/loader/Loader' -import AppButton from '~/components/app-button/AppButton' -import AppTextField from '~/components/app-text-field/AppTextField' -import { snackbarVariants } from '~/constants' -import useForm from '~/hooks/use-form' +import { useModalContext } from '~/context/modal-context' import useChangeUserStatus from '~/hooks/use-change-user-status' -import useAxios from '~/hooks/use-axios' -import { useAppDispatch, useAppSelector } from '~/hooks/use-redux' -import useInputVisibility from '~/hooks/use-input-visibility' -import useConfirm from '~/hooks/use-confirm' - -import { AuthService } from '~/services/auth-service' -import { openAlert } from '~/redux/features/snackbarSlice' - -import { styles } from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles' import PasswordSecurityItem from './password-security-item/PasswordSecurityItem' +import ChangePasswordModal from './change-password-modal/ChangePasswordModal' +import AppButton from '~/components/app-button/AppButton' -import { - ButtonVariantEnum, - ComponentEnum, - ButtonTypeEnum, - InputEnum, - SizeEnum, - FormValues, - ErrorResponse -} from '~/types' -import { - initialValues, - validations -} from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab.constants' +import { styles } from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles' +import { ButtonVariantEnum, SizeEnum } from '~/types' const PasswordSecurityTab: FC = () => { const { t } = useTranslation() - const dispatch = useAppDispatch() - const { userId } = useAppSelector((state) => state.appMain) - const { checkConfirmation } = useConfirm() - + const { openModal } = useModalContext() const { neededAction, checkStatusChange } = useChangeUserStatus() - const [isModalOpen, setIsModalOpen] = useState(false) - - const handleSubmitChangePassword = async () => { - const confirmed = await checkConfirmation({ - message: t( - 'editProfilePage.profile.passwordSecurityTab.changePasswordConfirm' - ), - title: 'titles.confirmTitle', - check: true - }) - if (confirmed) { - resetErrors() - await sendChangedPassword({ - password: data.password, - currentPassword: data.currentPassword - }) - } - } - - const handleResponse = () => { - dispatch( - openAlert({ - severity: snackbarVariants.success, - message: 'editProfilePage.profile.successMessage' - }) - ) - setIsModalOpen(false) - } - - const changePassword = useCallback( - (data: { password: string; currentPassword: string }) => { - return AuthService.changePassword(userId, data) - }, - [userId] - ) - - const handleResponseError = (error?: ErrorResponse) => { - if (error?.code === 'INCORRECT_CREDENTIALS') { - handleErrors('password', t('common.errorMessages.samePasswords')) - } else { - handleErrors('currentPassword', t('common.errorMessages.currentPassword')) - } - } - - const { loading, fetchData: sendChangedPassword } = useAxios({ - service: changePassword, - onResponse: handleResponse, - onResponseError: handleResponseError, - fetchOnMount: false - }) - - const { - data, - handleSubmit, - handleInputChange, - errors, - handleBlur, - resetData, - resetErrors, - handleErrors - } = useForm({ - onSubmit: handleSubmitChangePassword, - initialValues: initialValues, - validations: validations - }) - const openChangePasswordModal = () => { - setIsModalOpen(true) - } - - const closeChangePasswordModal = () => { - setIsModalOpen(false) + openModal({ + component: + }) } - const handleChangeStatusClick = () => { void checkStatusChange( `editProfilePage.profile.passwordSecurityTab.${neededAction}Title`, @@ -126,35 +28,6 @@ const PasswordSecurityTab: FC = () => { true ) } - - const { - inputVisibility: currentPasswordVisibility, - showInputText: showCurrentPassword - } = useInputVisibility(errors.currentPassword) - - const { inputVisibility: passwordVisibility, showInputText: showPassword } = - useInputVisibility(errors.password) - - const { - inputVisibility: newPasswordVisibility, - showInputText: showNewPassword - } = useInputVisibility(errors.confirmPassword) - - const onDiscard = () => { - resetData() - resetErrors() - closeChangePasswordModal() - } - - const saveButtonContent = loading ? ( - - ) : ( - t('editProfilePage.profile.passwordSecurityTab.savePassword') - ) - - const inputType = (isVisible: boolean) => - isVisible ? InputEnum.Text : InputEnum.Password - return ( { sx={styles.deactivateButton} title={t('editProfilePage.profile.passwordSecurityTab.title')} /> - - - - - {t('editProfilePage.profile.passwordSecurityTab.changePassword')} - - - {t( - 'editProfilePage.profile.passwordSecurityTab.changePasswordModalDescription' - )} - - - - - - - - - - {t('common.cancel')} - - - {saveButtonContent} - - - - - - { + const { t } = useTranslation() + const dispatch = useAppDispatch() + const { userId } = useAppSelector((state) => state.appMain) + const { checkConfirmation } = useConfirm() + const { closeModal } = useModalContext() + + const handleSubmitChangePassword = async () => { + const confirmed = await checkConfirmation({ + message: t( + 'editProfilePage.profile.passwordSecurityTab.changePasswordConfirm' + ), + title: 'titles.confirmTitle', + check: true + }) + if (confirmed) { + resetErrors() + await sendChangedPassword({ + password: data.password, + currentPassword: data.currentPassword + }) + } + } + + const handleResponse = () => { + dispatch( + openAlert({ + severity: snackbarVariants.success, + message: 'editProfilePage.profile.successMessage' + }) + ) + closeModal() + } + + const changePassword = useCallback( + (data: { password: string; currentPassword: string }) => { + return AuthService.changePassword(userId, data) + }, + [userId] + ) + + const handleResponseError = (error?: ErrorResponse) => { + if (error?.code === 'INCORRECT_CREDENTIALS') { + handleErrors('password', t('common.errorMessages.samePasswords')) + } else { + handleErrors('currentPassword', t('common.errorMessages.currentPassword')) + } + } + + const { loading, fetchData: sendChangedPassword } = useAxios({ + service: changePassword, + onResponse: handleResponse, + onResponseError: handleResponseError, + fetchOnMount: false + }) + + const { + data, + handleSubmit, + handleInputChange, + errors, + handleBlur, + resetData, + resetErrors, + handleErrors + } = useForm({ + onSubmit: handleSubmitChangePassword, + initialValues: initialValues, + validations: validations + }) + + const { + inputVisibility: currentPasswordVisibility, + showInputText: showCurrentPassword + } = useInputVisibility(errors.currentPassword) + + const { inputVisibility: passwordVisibility, showInputText: showPassword } = + useInputVisibility(errors.password) + + const { + inputVisibility: newPasswordVisibility, + showInputText: showNewPassword + } = useInputVisibility(errors.confirmPassword) + + const onDiscard = () => { + resetData() + resetErrors() + closeModal() + } + + const saveButtonContent = loading ? ( + + ) : ( + t('editProfilePage.profile.passwordSecurityTab.savePassword') + ) + + const inputType = (isVisible: boolean) => + isVisible ? InputEnum.Text : InputEnum.Password + + return ( + + + + {t('editProfilePage.profile.passwordSecurityTab.changePassword')} + + + {t( + 'editProfilePage.profile.passwordSecurityTab.changePasswordModalDescription' + )} + + + + + + + + + + {t('common.cancel')} + + + {saveButtonContent} + + + + + + ) +} + +export default ChangePasswordModal diff --git a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts index 4b5dd9f75..b1af738ef 100644 --- a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts +++ b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts @@ -15,6 +15,6 @@ export const styles = { color: 'primary.500' }, titlesAndButtonContainer: { - padding: '20px 0' + p: '20px 0' } } diff --git a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx index 4ab90fec6..f52497684 100644 --- a/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx +++ b/src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.tsx @@ -5,8 +5,8 @@ import { Theme } from '@emotion/react' import AppButton from '~/components/app-button/AppButton' import AppCard from '~/components/app-card/AppCard' -import { styles } from '~/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles' +import { styles } from '~/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles' import { ButtonVariantEnum, SizeEnum } from '~/types' interface PasswordSecurityItemProps { From 937d89d4698860a508d53223a89e8e42804e3b17 Mon Sep 17 00:00:00 2001 From: YulichkaR Date: Sun, 28 Jul 2024 20:11:59 +0300 Subject: [PATCH 3/4] Separated test files --- .../ChangePasswordModal.spec.jsx | 233 ++++++++++++++++++ .../PasswordSecurityTab.spec.jsx | 204 +-------------- 2 files changed, 236 insertions(+), 201 deletions(-) create mode 100644 tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx diff --git a/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx b/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx new file mode 100644 index 000000000..fe7e6a252 --- /dev/null +++ b/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx @@ -0,0 +1,233 @@ +import { vi } from 'vitest' +import { screen, fireEvent, waitFor } from '@testing-library/react' +import { + renderWithProviders, + mockAxiosClient, + TestSnackbar +} from '~tests/test-utils' +import PasswordSecurityTab from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab' +import { AuthService } from '~/services/auth-service' + +import { URLs } from '~/constants/request' + +const userDataMock = { + _id: 123456 +} + +vi.mock('~/services/auth-service', () => ({ + AuthService: { + changePassword: vi.fn() + } +})) + +const handleSubmit = vi.fn() + +const changeInputValue = (label, value) => { + fireEvent.change(label, { target: { value } }) +} + +const setupChangePasswordButton = () => { + const changePasswordButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.changePassword/i + ) + return { + changePasswordButton + } +} +const setupChangePasswordFields = () => { + const currentPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + const passwordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.newPassword/i + ) + const confirmPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.retypePassword/i + ) + const saveButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.savePassword/i + ) + + return { + currentPasswordInput, + passwordInput, + confirmPasswordInput, + saveButton + } +} + +const renderWithMockData = () => { + renderWithProviders( + + + , + { + preloadedState: { + appMain: { + userId: userDataMock._id, + userStatus: 'active' + } + } + } + ) +} + +describe('PasswordSecurityTab', () => { + beforeEach(() => { + renderWithMockData() + }) + + it('should save data after positive response', async () => { + mockAxiosClient + .onPatch(`${URLs.auth.changePassword}/${userDataMock._id}`) + .reply(200) + + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + + const { + currentPasswordInput, + passwordInput, + confirmPasswordInput, + saveButton + } = setupChangePasswordFields() + + fireEvent.change(currentPasswordInput, { + target: { value: '12345qwert' } + }) + fireEvent.change(passwordInput, { + target: { value: '12345qwertY' } + }) + fireEvent.change(confirmPasswordInput, { + target: { value: '12345qwertY' } + }) + + await waitFor(() => { + fireEvent.click(saveButton) + }) + + const confirmButton = screen.getByText('common.yes') + fireEvent.click(confirmButton) + + await waitFor(() => { + expect(AuthService.changePassword).toHaveBeenCalledWith( + userDataMock._id, + { + password: '12345qwertY', + currentPassword: '12345qwert' + } + ) + }) + }) + + it('should not save data after negative response', async () => { + mockAxiosClient + .onPatch(`${URLs.auth.changePassword}/${userDataMock}`) + .reply(400, { + message: 'new password cannot be the same as the current one' + }) + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + + const { + currentPasswordInput, + passwordInput, + confirmPasswordInput, + saveButton + } = setupChangePasswordFields() + + fireEvent.change(currentPasswordInput, { + target: { value: '12345qwertY' } + }) + fireEvent.change(passwordInput, { + target: { value: '12345qwertY' } + }) + fireEvent.change(confirmPasswordInput, { + target: { value: '12345qwertY' } + }) + + await waitFor(() => { + fireEvent.click(saveButton) + }) + expect(handleSubmit).not.toHaveBeenCalled() + }) + + it('should do not save empty fields', async () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + const { + currentPasswordInput, + passwordInput, + confirmPasswordInput, + saveButton + } = setupChangePasswordFields() + + fireEvent.change(currentPasswordInput, { + target: { value: '' } + }) + fireEvent.change(passwordInput, { + target: { value: '' } + }) + fireEvent.change(confirmPasswordInput, { + target: { value: '' } + }) + + fireEvent.click(saveButton) + + expect(handleSubmit).not.toHaveBeenCalled() + + expect(currentPasswordInput).toHaveValue('') + expect(passwordInput).toHaveValue('') + expect(confirmPasswordInput).toHaveValue('') + }) + + it('should show visibility icon', async () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + const visibilityOffIcons = screen.getAllByTestId('VisibilityOffIcon') + const visibilityOffIcon = visibilityOffIcons[0] + fireEvent.click(visibilityOffIcon) + + const visibilityIcons = screen.getAllByTestId('VisibilityIcon') + const visibilityIcon = visibilityIcons[0] + await waitFor(() => { + expect(visibilityIcon).toBeInTheDocument() + expect(visibilityOffIcon).not.toBeInTheDocument() + }) + }) + + it('resets form when discard button is clicked', () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + + const { passwordInput } = setupChangePasswordFields() + const discardButtonText = screen.getByText('common.cancel') + + changeInputValue(passwordInput, 'oldPassword') + + fireEvent.click(discardButtonText) + fireEvent.click(changePasswordButton) + + const cleanedPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + + expect(cleanedPasswordInput).toHaveValue('') + }) + + it('updates state when form fields are changed', () => { + const { changePasswordButton } = setupChangePasswordButton() + + fireEvent.click(changePasswordButton) + const { currentPasswordInput } = setupChangePasswordFields() + + changeInputValue(currentPasswordInput, 'oldPassword') + + expect(currentPasswordInput).toHaveValue('oldPassword') + }) +}) diff --git a/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx b/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx index 71aa31c1a..db441a7e9 100644 --- a/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx +++ b/tests/unit/containers/edit-profile/password-security-tab/PasswordSecurityTab.spec.jsx @@ -2,18 +2,10 @@ import { vi } from 'vitest' import { screen, fireEvent, - waitFor, waitForElementToBeRemoved } from '@testing-library/react' -import { - renderWithProviders, - mockAxiosClient, - TestSnackbar -} from '~tests/test-utils' +import { renderWithProviders, TestSnackbar } from '~tests/test-utils' import PasswordSecurityTab from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab' -import { AuthService } from '~/services/auth-service' - -import { URLs } from '~/constants/request' const userDataMock = { _id: 123456 @@ -25,42 +17,6 @@ vi.mock('~/services/auth-service', () => ({ } })) -const handleSubmit = vi.fn() - -const changeInputValue = (label, value) => { - fireEvent.change(label, { target: { value } }) -} - -const setupChangePasswordButton = () => { - const changePasswordButton = screen.getByText( - /editProfilePage.profile.passwordSecurityTab.changePassword/i - ) - return { - changePasswordButton - } -} -const setupChangePasswordFields = () => { - const currentPasswordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.currentPassword/i - ) - const passwordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.newPassword/i - ) - const confirmPasswordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.retypePassword/i - ) - const saveButton = screen.getByText( - /editProfilePage.profile.passwordSecurityTab.savePassword/i - ) - - return { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } -} - const renderWithMockData = () => { renderWithProviders( @@ -82,129 +38,6 @@ describe('PasswordSecurityTab', () => { renderWithMockData() }) - it('should save data after positive response', async () => { - mockAxiosClient - .onPatch(`${URLs.auth.changePassword}/${userDataMock._id}`) - .reply(200) - - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() - - fireEvent.change(currentPasswordInput, { - target: { value: '12345qwert' } - }) - fireEvent.change(passwordInput, { - target: { value: '12345qwertY' } - }) - fireEvent.change(confirmPasswordInput, { - target: { value: '12345qwertY' } - }) - - await waitFor(() => { - fireEvent.click(saveButton) - }) - - const confirmButton = screen.getByText('common.yes') - fireEvent.click(confirmButton) - - await waitFor(() => { - expect(AuthService.changePassword).toHaveBeenCalledWith( - userDataMock._id, - { - password: '12345qwertY', - currentPassword: '12345qwert' - } - ) - }) - }) - - it('should not save data after negative response', async () => { - mockAxiosClient - .onPatch(`${URLs.auth.changePassword}/${userDataMock}`) - .reply(400, { - message: 'new password cannot be the same as the current one' - }) - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() - - fireEvent.change(currentPasswordInput, { - target: { value: '12345qwertY' } - }) - fireEvent.change(passwordInput, { - target: { value: '12345qwertY' } - }) - fireEvent.change(confirmPasswordInput, { - target: { value: '12345qwertY' } - }) - - await waitFor(() => { - fireEvent.click(saveButton) - }) - expect(handleSubmit).not.toHaveBeenCalled() - }) - - it('should do not save empty fields', async () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() - - fireEvent.change(currentPasswordInput, { - target: { value: '' } - }) - fireEvent.change(passwordInput, { - target: { value: '' } - }) - fireEvent.change(confirmPasswordInput, { - target: { value: '' } - }) - - fireEvent.click(saveButton) - - expect(handleSubmit).not.toHaveBeenCalled() - - expect(currentPasswordInput).toHaveValue('') - expect(passwordInput).toHaveValue('') - expect(confirmPasswordInput).toHaveValue('') - }) - - it('should show visibility icon', async () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - const visibilityOffIcons = screen.getAllByTestId('VisibilityOffIcon') - const visibilityOffIcon = visibilityOffIcons[0] - fireEvent.click(visibilityOffIcon) - - const visibilityIcons = screen.getAllByTestId('VisibilityIcon') - const visibilityIcon = visibilityIcons[0] - await waitFor(() => { - expect(visibilityIcon).toBeInTheDocument() - expect(visibilityOffIcon).not.toBeInTheDocument() - }) - }) - it('renders title and description', () => { const title = screen.getByText( 'editProfilePage.profile.passwordSecurityTab.title' @@ -216,38 +49,7 @@ describe('PasswordSecurityTab', () => { expect(description).toBeInTheDocument() }) - it('resets form when discard button is clicked', () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - - const { passwordInput } = setupChangePasswordFields() - const discardButtonText = screen.getByText('common.cancel') - - changeInputValue(passwordInput, 'oldPassword') - - fireEvent.click(discardButtonText) - fireEvent.click(changePasswordButton) - - const cleanedPasswordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.currentPassword/i - ) - - expect(cleanedPasswordInput).toHaveValue('') - }) - - it('updates state when form fields are changed', () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - const { currentPasswordInput } = setupChangePasswordFields() - - changeInputValue(currentPasswordInput, 'oldPassword') - - expect(currentPasswordInput).toHaveValue('oldPassword') - }) - - it('checks is ConfirmDialog open when deactivate account button is clicked', () => { + it('checks if ConfirmDialog is open when deactivate account button is clicked', () => { const deactivateAccountButton = screen.getByText( 'editProfilePage.profile.passwordSecurityTab.deactivateAccount' ) @@ -260,7 +62,7 @@ describe('PasswordSecurityTab', () => { expect(deactivateTitle).toBeInTheDocument() }) - it('checks is ConfirmDialog closed when cancel button is clicked', async () => { + it('checks if ConfirmDialog is closed when cancel button is clicked', async () => { const deactivateAccountButton = screen.getByText( 'editProfilePage.profile.passwordSecurityTab.deactivateAccount' ) From 5edaf9c495129ba9c82433d98c2ce5c21dd30101 Mon Sep 17 00:00:00 2001 From: YulichkaR Date: Mon, 29 Jul 2024 20:23:11 +0300 Subject: [PATCH 4/4] fixed the logic in the test files and changed the paths using alias --- .../PasswordSecurityTab.tsx | 4 +- .../ChangePasswordModal.spec.jsx | 161 +++++++----------- 2 files changed, 66 insertions(+), 99 deletions(-) diff --git a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx index ca8b2d16c..a7b8b59fe 100644 --- a/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx +++ b/src/containers/edit-profile/password-security-tab/PasswordSecurityTab.tsx @@ -4,8 +4,8 @@ import Box from '@mui/material/Box' import { useModalContext } from '~/context/modal-context' import useChangeUserStatus from '~/hooks/use-change-user-status' -import PasswordSecurityItem from './password-security-item/PasswordSecurityItem' -import ChangePasswordModal from './change-password-modal/ChangePasswordModal' +import PasswordSecurityItem from '~/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem' +import ChangePasswordModal from '~/containers/edit-profile/password-security-tab/change-password-modal/ChangePasswordModal' import AppButton from '~/components/app-button/AppButton' import { styles } from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab.styles' diff --git a/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx b/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx index fe7e6a252..d466cc8fb 100644 --- a/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx +++ b/tests/unit/containers/edit-profile/password-security-tab/ChangePasswordModal.spec.jsx @@ -5,61 +5,26 @@ import { mockAxiosClient, TestSnackbar } from '~tests/test-utils' -import PasswordSecurityTab from '~/containers/edit-profile/password-security-tab/PasswordSecurityTab' +import ChangePasswordModal from '~/containers/edit-profile/password-security-tab/change-password-modal/ChangePasswordModal' import { AuthService } from '~/services/auth-service' - import { URLs } from '~/constants/request' const userDataMock = { _id: 123456 } +const handleSubmit = vi.fn() + vi.mock('~/services/auth-service', () => ({ AuthService: { changePassword: vi.fn() } })) -const handleSubmit = vi.fn() - -const changeInputValue = (label, value) => { - fireEvent.change(label, { target: { value } }) -} - -const setupChangePasswordButton = () => { - const changePasswordButton = screen.getByText( - /editProfilePage.profile.passwordSecurityTab.changePassword/i - ) - return { - changePasswordButton - } -} -const setupChangePasswordFields = () => { - const currentPasswordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.currentPassword/i - ) - const passwordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.newPassword/i - ) - const confirmPasswordInput = screen.getByLabelText( - /editProfilePage.profile.passwordSecurityTab.retypePassword/i - ) - const saveButton = screen.getByText( - /editProfilePage.profile.passwordSecurityTab.savePassword/i - ) - - return { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } -} - -const renderWithMockData = () => { +const renderChangePasswordModal = () => { renderWithProviders( - + , { preloadedState: { @@ -72,9 +37,9 @@ const renderWithMockData = () => { ) } -describe('PasswordSecurityTab', () => { +describe('ChangePasswordModal', () => { beforeEach(() => { - renderWithMockData() + renderChangePasswordModal() }) it('should save data after positive response', async () => { @@ -82,16 +47,18 @@ describe('PasswordSecurityTab', () => { .onPatch(`${URLs.auth.changePassword}/${userDataMock._id}`) .reply(200) - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() + const currentPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + const passwordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.newPassword/i + ) + const confirmPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.retypePassword/i + ) + const saveButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.savePassword/i + ) fireEvent.change(currentPasswordInput, { target: { value: '12345qwert' } @@ -123,20 +90,23 @@ describe('PasswordSecurityTab', () => { it('should not save data after negative response', async () => { mockAxiosClient - .onPatch(`${URLs.auth.changePassword}/${userDataMock}`) + .onPatch(`${URLs.auth.changePassword}/${userDataMock._id}`) .reply(400, { message: 'new password cannot be the same as the current one' }) - const { changePasswordButton } = setupChangePasswordButton() - fireEvent.click(changePasswordButton) - - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() + const currentPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + const passwordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.newPassword/i + ) + const confirmPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.retypePassword/i + ) + const saveButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.savePassword/i + ) fireEvent.change(currentPasswordInput, { target: { value: '12345qwertY' } @@ -151,19 +121,23 @@ describe('PasswordSecurityTab', () => { await waitFor(() => { fireEvent.click(saveButton) }) + expect(handleSubmit).not.toHaveBeenCalled() }) - it('should do not save empty fields', async () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - const { - currentPasswordInput, - passwordInput, - confirmPasswordInput, - saveButton - } = setupChangePasswordFields() + it('should not save empty fields', () => { + const currentPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + const passwordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.newPassword/i + ) + const confirmPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.retypePassword/i + ) + const saveButton = screen.getByText( + /editProfilePage.profile.passwordSecurityTab.savePassword/i + ) fireEvent.change(currentPasswordInput, { target: { value: '' } @@ -178,16 +152,12 @@ describe('PasswordSecurityTab', () => { fireEvent.click(saveButton) expect(handleSubmit).not.toHaveBeenCalled() - expect(currentPasswordInput).toHaveValue('') expect(passwordInput).toHaveValue('') expect(confirmPasswordInput).toHaveValue('') }) it('should show visibility icon', async () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) const visibilityOffIcons = screen.getAllByTestId('VisibilityOffIcon') const visibilityOffIcon = visibilityOffIcons[0] fireEvent.click(visibilityOffIcon) @@ -201,33 +171,30 @@ describe('PasswordSecurityTab', () => { }) it('resets form when discard button is clicked', () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - - const { passwordInput } = setupChangePasswordFields() - const discardButtonText = screen.getByText('common.cancel') - - changeInputValue(passwordInput, 'oldPassword') - - fireEvent.click(discardButtonText) - fireEvent.click(changePasswordButton) - - const cleanedPasswordInput = screen.getByLabelText( + const currentPasswordInput = screen.getByLabelText( /editProfilePage.profile.passwordSecurityTab.currentPassword/i ) + const passwordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.newPassword/i + ) + const confirmPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.retypePassword/i + ) + const discardButton = screen.getByText('common.cancel') + + fireEvent.change(passwordInput, { target: { value: 'oldPassword' } }) + fireEvent.click(discardButton) - expect(cleanedPasswordInput).toHaveValue('') + expect(currentPasswordInput).toHaveValue('') + expect(passwordInput).toHaveValue('') + expect(confirmPasswordInput).toHaveValue('') }) it('updates state when form fields are changed', () => { - const { changePasswordButton } = setupChangePasswordButton() - - fireEvent.click(changePasswordButton) - const { currentPasswordInput } = setupChangePasswordFields() - - changeInputValue(currentPasswordInput, 'oldPassword') - + const currentPasswordInput = screen.getByLabelText( + /editProfilePage.profile.passwordSecurityTab.currentPassword/i + ) + fireEvent.change(currentPasswordInput, { target: { value: 'oldPassword' } }) expect(currentPasswordInput).toHaveValue('oldPassword') }) })