From 0453d0c706df1106c612fc0ffdcac744ccd7f7cb Mon Sep 17 00:00:00 2001 From: oreoluwa212 Date: Mon, 22 Jul 2024 00:17:26 +0100 Subject: [PATCH] Revert "Revert "Feat/hng 207 user password settings page"" --- .../account-security/password/page.tsx | 156 ++++++++++++++++++ .../password/password-check.test.tsx | 106 ++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 src/app/settings/account-security/password/page.tsx create mode 100644 src/app/settings/account-security/password/password-check.test.tsx diff --git a/src/app/settings/account-security/password/page.tsx b/src/app/settings/account-security/password/page.tsx new file mode 100644 index 000000000..2d615294e --- /dev/null +++ b/src/app/settings/account-security/password/page.tsx @@ -0,0 +1,156 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { z } from "zod"; + +import PasswordCheck from "~/components/common/PasswordCheck"; +import { PasswordInput } from "~/components/common/PasswordInput"; +import PasswordSuccessfulModal from "~/components/modals/PasswordSuccessfulModal"; +import PasswordSettingsHeading from "~/components/passwordSettings/passwordSettingsHeading"; +import { Button } from "~/components/ui/button"; +import { Label } from "~/components/ui/label"; + +const passwordSchema = z + .object({ + currentPassword: z.string().min(1, "Current password is required"), + newPassword: z + .string() + .min(8, "New password must be at least 8 characters long") + .refine( + (value) => /[A-Z]/.test(value), + "New password must contain at least one uppercase letter", + ) + .refine( + (value) => /\d/.test(value), + "New password must contain at least one number", + ), + confirmPassword: z.string().min(1, "Confirm password is required"), + }) + .superRefine((data, context) => { + if (data.newPassword !== data.confirmPassword) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: "Passwords must match", + path: ["confirmPassword"], + }); + } + }); +type FormData = z.infer; +const PasswordSettings = () => { + const { + control, + handleSubmit, + formState: { errors }, + watch, + } = useForm({ + resolver: zodResolver(passwordSchema), + }); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isFocused, setIsFocused] = useState(false); + const newPasswordValue = watch("newPassword"); + const openModal = () => setIsModalOpen(true); + const closeModal = () => setIsModalOpen(false); + const onSubmit = () => { + openModal(); + }; + return ( +
+
+ +
+ + + ( + + )} + /> + {errors.currentPassword && ( +

{errors.currentPassword.message}

+ )} +
+ + + ( + { + field.onChange(data); + setIsFocused(true); + }} + name="new_password" + id="new_password" + placeholder="Enter new password" + /> + )} + /> + {isFocused && ( + {}} + /> + )} + {errors.newPassword && ( +

{errors.newPassword.message}

+ )} +
+ + + ( + + )} + /> + {errors.confirmPassword && ( +

{errors.confirmPassword.message}

+ )} +
+
+ + +
+
+ {isModalOpen && ( + + )} +
+
+ ); +}; +export default PasswordSettings; diff --git a/src/app/settings/account-security/password/password-check.test.tsx b/src/app/settings/account-security/password/password-check.test.tsx new file mode 100644 index 000000000..674a6079f --- /dev/null +++ b/src/app/settings/account-security/password/password-check.test.tsx @@ -0,0 +1,106 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import PasswordSettings from "./page"; + +describe("passwordSettings Component", () => { + it("renders all elements correctly", () => { + expect.assertions(7); + render(); + + expect(screen.getByText("Password Settings")).toBeInTheDocument(); + expect( + screen.getByText("Update password for enhanced account security"), + ).toBeInTheDocument(); + expect(screen.getByLabelText("Current Password")).toBeInTheDocument(); + expect(screen.getByLabelText("New Password")).toBeInTheDocument(); + expect(screen.getByLabelText("Confirm New Password")).toBeInTheDocument(); + expect(screen.getByText("Cancel")).toBeInTheDocument(); + expect(screen.getByText("Update Password")).toBeInTheDocument(); + }); + + it("verifies responsiveness across various devices", () => { + expect.assertions(1); + render(); + + expect(screen.getByText("Password Settings")).toBeInTheDocument(); + }); + + it("tests password update functionality", async () => { + expect.assertions(6); + render(); + const user = userEvent.setup(); + + const currentPasswordInput = screen.getByLabelText("Current Password"); + const newPasswordInput = screen.getByLabelText("New Password"); + const confirmPasswordInput = screen.getByLabelText("Confirm New Password"); + + await user.type(currentPasswordInput, "currentpassword"); + await user.type(newPasswordInput, "Newpassword1!"); + await user.type(confirmPasswordInput, "Newpassword1!"); + + expect(currentPasswordInput).toHaveValue("currentpassword"); + expect(newPasswordInput).toHaveValue("Newpassword1!"); + expect(confirmPasswordInput).toHaveValue("Newpassword1!"); + + await user.clear(confirmPasswordInput); + await user.type(confirmPasswordInput, "differentpassword"); + await user.click(screen.getByText("Update Password")); + + await waitFor(() => { + expect(screen.getByText("Passwords must match")).toBeInTheDocument(); + }); + + await user.clear(newPasswordInput); + await user.clear(confirmPasswordInput); + await user.type(newPasswordInput, "weakpass1"); + await user.type(confirmPasswordInput, "weakpass1"); + await user.click(screen.getByText("Update Password")); + + await waitFor(() => { + expect( + screen.getByText( + "New password must contain at least one uppercase letter", + ), + ).toBeInTheDocument(); + }); + + await user.clear(newPasswordInput); + await user.clear(confirmPasswordInput); + await user.type(newPasswordInput, "Weakpassword"); + await user.type(confirmPasswordInput, "Weakpassword"); + await user.click(screen.getByText("Update Password")); + + await waitFor(() => { + expect( + screen.getByText("New password must contain at least one number"), + ).toBeInTheDocument(); + }); + }, 10_000); + + it("confirms successful password update workflow", async () => { + expect.assertions(2); + render(); + const user = userEvent.setup(); + + const currentPasswordInput = screen.getByLabelText("Current Password"); + const newPasswordInput = screen.getByLabelText("New Password"); + const confirmPasswordInput = screen.getByLabelText("Confirm New Password"); + + await user.type(currentPasswordInput, "currentpassword"); + await user.type(newPasswordInput, "Newpassword1!"); + await user.type(confirmPasswordInput, "Newpassword1!"); + + await user.click(screen.getByText("Update Password")); + await waitFor(() => { + expect( + screen.getByText("Password Successfully Updated!"), + ).toBeInTheDocument(); + }); + + await user.click(screen.getByText("Continue")); + expect( + screen.queryByText("Password Successfully Updated!"), + ).not.toBeInTheDocument(); + }); +});