diff --git a/mocks/handler.js b/mocks/handler.js index 3a8f75d..65f5398 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -1,10 +1,17 @@ -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; +import {GET_USER_ROUTE, SET_USER_PASSWORD} from "@/core/data/apiRoutes"; import {rest} from "msw"; -export const handler = [rest.get(GET_USER_ROUTE, (req, res, ctx) => { - return res(ctx.json({ - data: { - id: 10 - } - })) -})] \ No newline at end of file +export const handler = [ + rest.get(GET_USER_ROUTE, (req, res, ctx) => { + return res(ctx.json({ + data:{ + id: 10 + } + })) + }), + rest.post(SET_USER_PASSWORD, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), +] \ No newline at end of file diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index e813849..3613a1c 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -80,7 +80,9 @@ "label_current_password": "رمز عبور فعلی", "label_new_password": "رمز عبور جدید", "label_confirm_password": "تکرار رمز عبور جدید", - "error_message_required": "اجباری !", + "error_message_current_password_required": "وارد کردن رمز عبور فعلی الزامیست!", + "error_message_new_password_required": "وارد کردن رمز عبور جدید الزامیست!", + "error_message_confirm_password_required": "وارد کردن تکرار رمز عبور جدید الزامیست!", "error_message_password_length": "رمز عبور باید حداقل 8 کاراکتر باشد.", "error_message_password_match": "پسورد ها یکسان هستند", "error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد" diff --git a/src/components/dashboard/change-password/change-password-form/__test__/index.test.js b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js new file mode 100644 index 0000000..503b571 --- /dev/null +++ b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js @@ -0,0 +1,305 @@ +import { render, screen, waitFor, fireEvent , act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; +import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; +import {server} from "../../../../../../mocks/server"; +import {rest} from "msw"; + +describe('Change Password Form Component From Change Password Page', () => { + describe('Rendering', () => { + it('Should see change password heading',() => { + render(); + const changePasswordElement = screen.getByRole('heading', { level: 4 }); + expect(changePasswordElement).toHaveTextContent('تغییر رمز عبور'); + }); + it('Should see the label for current password field', () => { + render(); + const currentPasswordLabel = screen.queryByLabelText('رمز عبور فعلی'); + expect(currentPasswordLabel).toBeInTheDocument(); + }); + it('Should see the label for new password field', () => { + render(); + const newPasswordLabel = screen.queryByLabelText('رمز عبور جدید'); + expect(newPasswordLabel).toBeInTheDocument(); + }); + it('Should see the label for confirm password field', () => { + render(); + const confirmPasswordLabel = screen.queryByLabelText('تکرار رمز عبور جدید'); + expect(confirmPasswordLabel).toBeInTheDocument(); + }); + it('Should see the submit button with the submit label and should be disable', () => { + render(); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + expect(submitButton).toBeInTheDocument(); + expect(submitButton).toBeDisabled(); + }); + it('Should have empty input fields and not see any validation error for current password, new password, and confirm password', () => { + render(); + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + expect(currentPasswordInput).toHaveValue(''); + expect(newPasswordInput).toHaveValue(''); + expect(confirmPasswordInput).toHaveValue(''); + + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument(); + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + + }); + }); + describe("Behavior", ()=>{ + it('Should fill the current password field and not see any error while its not empty', async () => { + render(); + + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + + // Simulate typing something in the current password input + fireEvent.change(currentPasswordInput, { target: { value: 'witel@fani0' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument(); + expect(currentPasswordInput).toHaveValue('witel@fani0'); + }); + }); + it('Should fill the new password field and not see any error while its not empty', async () => { + render(); + + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + + // Simulate typing something in the new password input + fireEvent.change(newPasswordInput, { target: { value: 'witel@fani1' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(newPasswordInput).toHaveValue('witel@fani1'); + }); + }); + it('Should fill the confirm new password field and not see any error while its not empty', async () => { + render(); + + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + // Simulate typing something in the confirmation new password input + fireEvent.change(confirmPasswordInput, { target: { value: 'witel@fani1' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(confirmPasswordInput).toHaveValue('witel@fani1'); + }); + }); + }); + describe("Validation", ()=>{ + it('Should see error while current password is empty on blur event', async () => { + render(); + + // Simulate blur event on the current password input + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + + // Clear the input field + fireEvent.change(currentPasswordInput, { target: { value: '' } }); + + fireEvent.blur(currentPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument() + }); + }); + it('Should see error while new password is empty on blur event', async () => { + render(); + + // Simulate blur event on the new password input + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + + // Clear the input field + fireEvent.change(newPasswordInput, { target: { value: '' } }); + + fireEvent.blur(newPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument() + }); + }); + it('Should see error while confirm new password is empty on blur event', async () => { + render(); + + // Simulate blur event on the new password input + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + // Clear the input field + fireEvent.change(confirmPasswordInput, { target: { value: '' } }); + + fireEvent.blur(confirmPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument() + }); + }); + it('Should see error when new password is less than 8 characters', async () => { + render(); + + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a short new password + fireEvent.change(newPasswordInput, { target: { value: 'test' } }); + fireEvent.blur(newPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + }); + }); + it('Should not see error when new password are 8 or more characters', async () => { + render(); + + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument(); + }); + }); + it('Should see error when new password and confirm password do not match on blur event and submit click', async () => { + render(); + + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + // Simulate entering a different value in the confirm password field + fireEvent.change(confirmPasswordInput, { target: { value: 'MismatchedPassword' } }); + fireEvent.blur(confirmPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + }); + }); + it('Should not see error when new password and confirm password match on blur event and submit click', async () => { + render(); + + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + // Simulate entering the same value in the confirmation password field + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(confirmPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should enable the submit button when the inputs are valid', async () => { + render(); + + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering valid values in the form fields + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Check if the submit button is enabled + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); + }); + it('Should request to api and resetform in success response', async () => { + // Render the component + render(); + + // Fill in the form fields + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Submit the form + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + await userEvent.click(submitButton); + + await waitFor(() => { + expect(currentPasswordInput).toHaveValue(''); + expect(newPasswordInput).toHaveValue(''); + expect(confirmPasswordInput).toHaveValue(''); + }); + }); + it('Should request to api and not resetform in error response', async () => { + // Render the component + render(); + server.use([rest.get(SET_USER_PASSWORD, (req, res, ctx) => { + return res(ctx.status(422)) + })]) + + // Fill in the form fields wrong + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'incorrect' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Submit the form + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + await userEvent.click(submitButton); + + await waitFor(() => { + expect(currentPasswordInput.value).toBe('incorrect'); + expect(newPasswordInput.value).toBe('Witel@fani1'); + expect(confirmPasswordInput.value).toBe('Witel@fani1'); + }); + }); + }); +}) \ No newline at end of file diff --git a/src/components/dashboard/change-password/change-password-form/index.js b/src/components/dashboard/change-password/change-password-form/index.js new file mode 100644 index 0000000..9b7704a --- /dev/null +++ b/src/components/dashboard/change-password/change-password-form/index.js @@ -0,0 +1,137 @@ +import {Formik, Form} from "formik"; +import * as Yup from "yup"; +import {useTranslations} from "next-intl"; +import {Button, Paper, Stack, Typography} from "@mui/material"; +import PasswordField from "@/core/components/PasswordField"; +import StyledForm from "@/core/components/StyledForm"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; + +const ChangePasswordForm = ({onSubmit}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}) + + const handleSubmit = (values, {setSubmitting, resetForm}) => { + requestServer(SET_USER_PASSWORD, 'post', { + data: { + current_password: values.current_password, + new_password: values.new_password, + new_password_confirmation: values.new_password_confirmation, + }, + }).then((response) => { + resetForm(); + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }; + const initialValues = { + current_password: "", + new_password: "", + new_password_confirmation: "", + }; + const validationSchema = Yup.object().shape({ + current_password: Yup.string().required( + t("ChangePassword.error_message_current_password_required") + ), + new_password: Yup.string() + .min(8, t("ChangePassword.error_message_password_length")) + .required(t("ChangePassword.error_message_new_password_required")), + new_password_confirmation: Yup.string() + .required(t("ChangePassword.error_message_confirm_password_required")) + .test( + t("ChangePassword.error_message_password_match"), + t("ChangePassword.error_message_password_not_match"), // Use the correct error message here + function (value) { + return this.parent.new_password === value; + } + ), + }); + + return ( + + {(props) => ( + { + e.preventDefault(); + props.handleSubmit(); + }} + > + + + + {t("ChangePassword.typography_change_password")} + + + + + + + + + + + + + + + )} + + ); +}; + +export default ChangePasswordForm; diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index a69a969..b616274 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,146 +1,15 @@ import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/DashboardLayout"; -import {Button, Container, Paper, Stack, Typography,} from "@mui/material"; -import {Formik} from "formik"; -import * as Yup from "yup"; -import {useTranslations} from "next-intl"; -import PasswordField from "@/core/components/PasswordField"; -import StyledForm from "@/core/components/StyledForm"; -import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; -import SvgChangePassword from "@/core/components/svgs/SvgChangePassword"; -import useRequest from "@/lib/app/hooks/useRequest"; +import DashboardLayouts from "@/layouts/dashboardLayouts"; +import {Container} from "@mui/material"; +import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; const DashboardChangePasswordComponent = () => { - const t = useTranslations(); - const requestServer = useRequest({auth: true}) - - const handleSubmit = (values, {setSubmitting, resetForm}) => { - requestServer(SET_USER_PASSWORD, 'post', { - data: { - current_password: values.current_password, - new_password: values.new_password, - new_password_confirmation: values.new_password_confirmation, - }, - }).then((response) => { - resetForm(); - }).catch(() => { - }).finally(() => { - setSubmitting(false); - }); - }; - const initialValues = { - current_password: "", - new_password: "", - new_password_confirmation: "", - }; - const validationSchema = Yup.object().shape({ - current_password: Yup.string().required( - t("ChangePassword.error_message_required") - ), - new_password: Yup.string() - .min(8, t("ChangePassword.error_message_password_length")) - .required(t("ChangePassword.error_message_required")), - new_password_confirmation: Yup.string() - .required(t("ChangePassword.error_message_required")) - .test( - t("ChangePassword.error_message_password_match"), - t("ChangePassword.error_message_password_not_match"), - function (value) { - return this.parent.new_password === value; - } - ), - }); return ( - - {(props) => ( - { - e.preventDefault(); - props.handleSubmit(); - }} - > - - - - - {t("ChangePassword.typography_change_password")} - - - - - - - - - - - - - - - )} - + diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index b96b998..16b2868 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -5,7 +5,7 @@ export const GET_USER_TOKEN = BASE_URL + "/api/auth/login"; //end login //change password -export const SET_USER_PASSWORD = BASE_URL + "/dashboard/profile/change_password"; +export const SET_USER_PASSWORD = BASE_URL + "/api/profile/change_password"; //end change password //user data