diff --git a/jest.setup.js b/jest.setup.js index 4298493..7d55f67 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,5 +1,6 @@ import '@testing-library/jest-dom' import {server} from "./mocks/server"; +import mockRouter from 'next-router-mock' require('dotenv').config({path: './.env.local'}); @@ -10,6 +11,8 @@ beforeAll(() => { }) beforeEach(() => { localStorage.clear(); + mockRouter.query = {} + }) afterEach(() => { diff --git a/src/components/login-expert/LoginExpertComponent.jsx b/src/components/login-expert/LoginExpertComponent.jsx new file mode 100644 index 0000000..9be301b --- /dev/null +++ b/src/components/login-expert/LoginExpertComponent.jsx @@ -0,0 +1,123 @@ +import {Box, Button, Container, Paper, Stack, TextField, Typography} from "@mui/material"; +import {Field, Formik} from "formik"; +import SvgLogin from "@/core/components/svgs/SvgLogin"; +import StyledForm from "@/core/components/StyledForm"; +import PasswordField from "@/core/components/PasswordField"; +import LoginIcon from "@mui/icons-material/Login"; +import {GET_USER_TOKEN} from "@/core/data/apiRoutes"; +import * as Yup from "yup"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useUser from "@/lib/app/hooks/useUser"; +import {useTranslations} from "next-intl"; + +const LoginExpertComponent = () =>{ + const t = useTranslations(); + const requestServer = useRequest() + const {setToken} = useUser(); // pass token to set token + const handleSubmit = (values, props) => { + requestServer(GET_USER_TOKEN, 'post', { + data: { + username: values.username, + password: values.password, + }, + success: { + notification: {show: false} + } + }).then((response) => { + setToken(response.data.token) + }).catch(() => { + props.setSubmitting(false) + }) + }; + const initialValues = { + username: "", + password: "", + }; + const validationSchema = Yup.object().shape({ + username: Yup.string().required(t("LoginPage.username_error_message_required")), + password: Yup.string().required(t("LoginPage.password_error_message_required")), + }); + + return( + + + + {(props) => ( + + + + + + {t("login_expert")} + + + + + + + + + + + + )} + + + + ) +} +export default LoginExpertComponent \ No newline at end of file diff --git a/src/components/login-expert/LoginLinkRouting.jsx b/src/components/login-expert/LoginLinkRouting.jsx new file mode 100644 index 0000000..1930b3c --- /dev/null +++ b/src/components/login-expert/LoginLinkRouting.jsx @@ -0,0 +1,28 @@ +import LinkRouting from "@/core/components/LinkRouting"; +import {Stack} from "@mui/material"; +import {useRouter} from "next/router"; +import {useTranslations} from "next-intl"; + +const LoginLinkRouting = () => { + const t = useTranslations(); + const router = useRouter() + const backUrlDecodedPath = router.query?.back_url + + return ( + + + {t("LoginPage.link_routing_back_to")}{" "} + {backUrlDecodedPath + ? t("LoginPage.link_routing_previuos_page") + : t("LoginPage.link_routing_main_page")} + + + ) +} +export default LoginLinkRouting \ No newline at end of file diff --git a/src/components/login-expert/__test__/LoginExpertComponent.test.js b/src/components/login-expert/__test__/LoginExpertComponent.test.js new file mode 100644 index 0000000..d76e49d --- /dev/null +++ b/src/components/login-expert/__test__/LoginExpertComponent.test.js @@ -0,0 +1,153 @@ +import {act, findByText, fireEvent, getByText, render, screen, waitFor} from '@testing-library/react'; +import MockAppWithProviders from '../../../../mocks/AppWithProvider'; +import mockRouter from 'next-router-mock' +import LoginExpertComponent from "@/components/login-expert/LoginExpertComponent"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; + +describe('Login expert component from login page', () => { + + describe('Rendering', () => { + it('LoginExpertComponent should see login expert text on the page',() => { + render( + + + + ); + const loginExpertElement = screen.getByRole('heading', { level: 4 }); + expect(loginExpertElement).toHaveTextContent('ورود کارشناس'); + }); + + it('LoginExpertComponent from LoginComponent Button should render login text', () => { + render( + + + + ); + const button = screen.getByText("ورود"); + // Assertions for button props + expect(button).toHaveAttribute('type', 'submit'); + expect(button).toHaveTextContent('ورود'); + }); + + it("Show error when username is empty", async ()=>{ + render( + + + + ); + + const usernameInput = screen.getByLabelText("نام کاربری") + + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).not.toBeInTheDocument() + + fireEvent.blur(usernameInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).toBeInTheDocument() + }) + + fireEvent.change(usernameInput, {target : {value : "testuser"}}) + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).not.toBeInTheDocument() + }) + }) + it("Show error when password is empty", async ()=>{ + render( + + + + ); + + const passwordInput = screen.getByLabelText("رمز عبور") + + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).not.toBeInTheDocument() + + fireEvent.blur(passwordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).toBeInTheDocument() + }) + + fireEvent.change(passwordInput, {target : {value : "testuser"}}) + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).not.toBeInTheDocument() + }) + }) + }); + + describe("Behavior", ()=>{ + it('Should fill the username field', async () => { + render( + + + + ); + // Get username and password input elements by their name attributes + const usernameInput = screen.getByLabelText('نام کاربری'); + + fireEvent.change(usernameInput, {target: {value: 'testuser'}}); + await act(async ()=>{ + // Simulate user input + expect(usernameInput).toHaveValue('testuser'); + }) + }); + + it('Should fill the password fields', async () => { + render( + + + + ); + // Get username and password input elements by their name attributes + const passwordInput = screen.getByLabelText('رمز عبور'); + fireEvent.change(passwordInput, {target: {value: 'password123'}}); + + await act(async ()=>{ + // Simulate user input + expect(passwordInput).toHaveValue('password123'); + }) + }); + + // this test is for when back url does not exist to run this test you should comment if in mock func + it('Should get main page when back url dos not exist', async () => { + render( + + + + ) + const linkElement = await screen.findByTestId("link_routing") + expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی") + }); + + }) + describe("validation", ()=>{ + it("Disabled submit button until fields completed", async ()=> { + render( + + + + ) + const usernameInput = screen.getByLabelText('نام کاربری'); + const passwordInput = screen.getByLabelText('رمز عبور'); + const submitButton = screen.getByText('ورود'); + + expect(submitButton).toBeDisabled(); + + fireEvent.change(usernameInput, {target: {value: 'testuser'}}); + + await waitFor(async () => { + // Now, the submit button should be enabled because both fields are completed + expect(submitButton).toBeDisabled(); + }); + + fireEvent.change(passwordInput, {target: {value: 'password123'}}); + + await waitFor(async () => { + // Now, the submit button should be enabled because both fields are completed + expect(submitButton).toBeEnabled(); + }); + }) + }) +}); \ No newline at end of file diff --git a/src/components/login-expert/__test__/LoginLinkRouting.test.js b/src/components/login-expert/__test__/LoginLinkRouting.test.js new file mode 100644 index 0000000..a63d314 --- /dev/null +++ b/src/components/login-expert/__test__/LoginLinkRouting.test.js @@ -0,0 +1,18 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../mocks/AppWithProvider"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; + +describe("Login expert component from login page",()=>{ + + describe('Rendering', () => { + it('LoginLinkRouting from LoginComponent Should get main page when back url dos not exist', async () => { + render( + + + + ) + const linkElement = await screen.findByTestId("link_routing") + expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی") + }); + }) +}) diff --git a/src/components/login-expert/index.jsx b/src/components/login-expert/index.jsx index 9f22a5b..dc36d9a 100644 --- a/src/components/login-expert/index.jsx +++ b/src/components/login-expert/index.jsx @@ -1,151 +1,16 @@ -import LinkRouting from "@/core/components/LinkRouting"; -import PasswordField from "@/core/components/PasswordField"; -import StyledForm from "@/core/components/StyledForm"; -import {GET_USER_TOKEN} from "@/core/data/apiRoutes"; import CenterLayout from "@/layouts/CenterLayout"; import FullPageLayout from "@/layouts/FullPageLayout"; -import useUser from "@/lib/app/hooks/useUser"; -import LoginIcon from "@mui/icons-material/Login"; -import {Box, Button, Container, Paper, Stack, TextField, Typography,} from "@mui/material"; -import {Field, Formik} from "formik"; -import {useTranslations} from "next-intl"; -import {useSearchParams} from "next/navigation"; -import * as Yup from "yup"; -import useDirection from "@/lib/app/hooks/useDirection"; -import SvgLogin from "@/core/components/svgs/SvgLogin"; -import useRequest from "@/lib/app/hooks/useRequest"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; +import LoginExpertComponent from "@/components/login-expert/LoginExpertComponent"; const LoginComponent = () => { - const t = useTranslations(); - const {directionApp} = useDirection(); // should delete because we don't have direction anymore - const {setToken} = useUser(); // pass token to set token - const requestServer = useRequest() - - // getting url query - const searchParams = useSearchParams(); - const backUrlDecodedPath = searchParams.get("back_url"); - - //formik properties - const handleSubmit = (values, props) => { - requestServer(GET_USER_TOKEN, 'post', { - data: { - username: values.username, - password: values.password, - }, - success: { - notification: {show: false} - } - }).then((response) => { - setToken(response.data.token) - }).catch(() => { - props.setSubmitting(false) - }) - }; - const initialValues = { - username: "", - password: "", - }; - const validationSchema = Yup.object().shape({ - username: Yup.string().required(t("LoginPage.username_error_message_required")), - password: Yup.string().required(t("LoginPage.password_error_message_required")), - }); return ( - - - - {(props) => ( - - - - - - {t("login_expert")} - - - - - - - - - - - - )} - - - + - - - {t("LoginPage.link_routing_back_to")}{" "} - {backUrlDecodedPath - ? t("LoginPage.link_routing_previuos_page") - : t("LoginPage.link_routing_main_page")} - - + ); }; diff --git a/src/core/components/PasswordField/__test__/index.test.js b/src/core/components/PasswordField/__test__/index.test.js new file mode 100644 index 0000000..479f91f --- /dev/null +++ b/src/core/components/PasswordField/__test__/index.test.js @@ -0,0 +1,31 @@ +import {fireEvent, getByRole, render, screen} from "@testing-library/react"; +import PasswordField from "@/core/components/PasswordField"; +import {Formik} from "formik"; + +describe('Password Field component in core folder', () => { + describe('Rendering', () => { + it('Show password when visibility icon clicked',() => { + render ( + {}}> + {(props)=>( + + )} + + ) + + const iconElement = screen.getByLabelText("رمز عبور") + const iconButton = screen.getByRole("button") + + + expect(iconElement).toHaveAttribute('type', 'password'); + + fireEvent.click(iconButton); + + expect(iconElement).toHaveAttribute("type", "text"); + + fireEvent.click(iconButton); + + expect(iconElement).toHaveAttribute('type', 'password'); + }) + }) +}) \ No newline at end of file diff --git a/src/core/components/PasswordField.jsx b/src/core/components/PasswordField/index.jsx similarity index 100% rename from src/core/components/PasswordField.jsx rename to src/core/components/PasswordField/index.jsx