From 83fbf83bae6e67789d6e3d3d4b5862f2bccaa98c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Mar 2026 20:57:35 +0300 Subject: [PATCH] feat(RegisterForm): add basic RegisterFormComponent with test coverage and storybook placeholder --- .../ui/RegisterForm/RegisterForm.spec.tsx | 115 ++++++++++++++++++ .../ui/RegisterForm/RegisterForm.stories.ts | 12 ++ .../auth/ui/RegisterForm/RegisterForm.tsx | 61 +++++++++- 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx create mode 100644 src/features/auth/ui/RegisterForm/RegisterForm.stories.ts diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx b/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx new file mode 100644 index 0000000..367301f --- /dev/null +++ b/src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx @@ -0,0 +1,115 @@ +import { render, screen } from "@testing-library/react"; +import { RegisterForm } from "./RegisterForm"; +import { selectAuthData, useAuthStore } from "../../model"; +import { MOCK_NEW_EMAIL, MOCK_PASSWORD } from "../../api"; +import userEvent from "@testing-library/user-event"; + +describe("RegisterForm", () => { + afterEach(() => { + useAuthStore.getState().reset(); + vi.restoreAllMocks(); + }); + + it("should render form", () => { + render(); + + const form = screen.getByRole("form"); + expect(form).toBeInTheDocument(); + }); + + it("should disable button when form is invalid", async () => { + render(); + + const registerButton = screen.getByRole("button", { name: /register/i }); + + expect(registerButton).toBeDisabled(); + }); + + it("should disable button when auth store status equals loading", async () => { + useAuthStore.setState({ status: "loading" }); + + render(); + + const emailInput = screen.getByRole("textbox", { name: /email/i }); + const passwordInput = screen.getByLabelText(/password/i); + const confirmPasswordInput = screen.getByLabelText(/confirm/i); + const registerButton = screen.getByRole("button", { name: /register/i }); + + await userEvent.type(emailInput, MOCK_NEW_EMAIL); + await userEvent.type(passwordInput, MOCK_PASSWORD); + await userEvent.type(confirmPasswordInput, MOCK_PASSWORD); + + expect(registerButton).toBeDisabled(); + }); + + it("should disable button when password and confirm password do not match", async () => { + render(); + + const emailInput = screen.getByRole("textbox", { name: /email/i }); + const passwordInput = screen.getByLabelText(/password/i); + const confirmPasswordInput = screen.getByLabelText(/confirm/i); + const registerButton = screen.getByRole("button", { name: /register/i }); + + await userEvent.type(emailInput, MOCK_NEW_EMAIL); + await userEvent.type(passwordInput, MOCK_PASSWORD); + await userEvent.type(confirmPasswordInput, MOCK_PASSWORD + "1"); + + expect(registerButton).toBeDisabled(); + }); + + it("should enable button when password and confirm password match and auth store status isn't equal to loading", async () => { + useAuthStore.setState({ status: "idle" }); + + render(); + + const emailInput = screen.getByRole("textbox", { name: /email/i }); + const passwordInput = screen.getByLabelText(/password/i); + const confirmPasswordInput = screen.getByLabelText(/confirm/i); + const registerButton = screen.getByRole("button", { name: /register/i }); + + await userEvent.type(emailInput, MOCK_NEW_EMAIL); + await userEvent.type(passwordInput, MOCK_PASSWORD); + await userEvent.type(confirmPasswordInput, MOCK_PASSWORD); + + expect(registerButton).not.toBeDisabled(); + }); + + it("should change email value in auth store when user types", async () => { + render(); + const emailInput = screen.getByRole("textbox", { name: /email/i }); + await userEvent.type(emailInput, MOCK_NEW_EMAIL); + + const storeEmailValue = selectAuthData(useAuthStore.getState()).email; + + expect(storeEmailValue).toBe(MOCK_NEW_EMAIL); + }); + + it("should change password value in auth store when user types", async () => { + render(); + const passwordInput = screen.getByLabelText(/password/i); + await userEvent.type(passwordInput, MOCK_PASSWORD); + + const storePasswordValue = selectAuthData(useAuthStore.getState()).password; + + expect(storePasswordValue).toBe(MOCK_PASSWORD); + }); + + it("should perform register api call when user clicks register button", async () => { + const registerSpy = vi.spyOn(useAuthStore.getState(), "register"); + useAuthStore.setState({ status: "idle" }); + + render(); + + const emailInput = screen.getByRole("textbox", { name: /email/i }); + const passwordInput = screen.getByLabelText(/password/i); + const confirmPasswordInput = screen.getByLabelText(/confirm/i); + const registerButton = screen.getByRole("button", { name: /register/i }); + + await userEvent.type(emailInput, MOCK_NEW_EMAIL); + await userEvent.type(passwordInput, MOCK_PASSWORD); + await userEvent.type(confirmPasswordInput, MOCK_PASSWORD); + await userEvent.click(registerButton); + + expect(registerSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts b/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts new file mode 100644 index 0000000..d9e36b6 --- /dev/null +++ b/src/features/auth/ui/RegisterForm/RegisterForm.stories.ts @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RegisterForm } from "./RegisterForm"; + +const meta: Meta = { + component: RegisterForm, + title: "features/auth/RegisterForm", +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/features/auth/ui/RegisterForm/RegisterForm.tsx b/src/features/auth/ui/RegisterForm/RegisterForm.tsx index 65b0a22..f12da9f 100644 --- a/src/features/auth/ui/RegisterForm/RegisterForm.tsx +++ b/src/features/auth/ui/RegisterForm/RegisterForm.tsx @@ -1,5 +1,64 @@ +import { useState, type ChangeEvent, type SubmitEvent } from "react"; +import { + selectFormValid, + selectStatusIsLoading, + useAuthStore, +} from "../../model"; + +/** + * Register form component + */ export function RegisterForm() { + const { formData, setEmail, setPassword, register } = useAuthStore(); + + const [confirmedPassword, setConfirmedPassword] = useState(""); + + const formValid = useAuthStore(selectFormValid); + const isLoading = useAuthStore(selectStatusIsLoading); + + const passwordMatch = formData?.password?.value === confirmedPassword; + const disabled = isLoading || !formValid || !passwordMatch; + + const handleEmailChange = (e: ChangeEvent) => { + setEmail(e.target.value); + }; + + const handlePasswordChange = (e: ChangeEvent) => { + setPassword(e.target.value); + }; + + const handleConfirmChange = (e: ChangeEvent) => { + setConfirmedPassword(e.target.value); + }; + + const handleSubmit = (e: SubmitEvent) => { + e.preventDefault(); + register(); + }; + return ( -
Register Form
+
+ + + + +
); }