From e4630a7fcbba2eba2a71544134798ebbdd603c0b Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 24 Mar 2026 19:30:45 +0300 Subject: [PATCH] feat(LoginForm): create LoginForm component with basic logic, test coverage and storybook placeholder --- .../auth/ui/LoginForm/LoginForm.spec.tsx | 58 +++++++++++++++++++ .../auth/ui/LoginForm/LoginForm.stories.ts | 12 ++++ src/features/auth/ui/LoginForm/LoginForm.tsx | 41 ++++++++++++- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/features/auth/ui/LoginForm/LoginForm.spec.tsx create mode 100644 src/features/auth/ui/LoginForm/LoginForm.stories.ts diff --git a/src/features/auth/ui/LoginForm/LoginForm.spec.tsx b/src/features/auth/ui/LoginForm/LoginForm.spec.tsx new file mode 100644 index 0000000..447259d --- /dev/null +++ b/src/features/auth/ui/LoginForm/LoginForm.spec.tsx @@ -0,0 +1,58 @@ +import { selectAuthData, useAuthStore } from "../../model"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { LoginForm } from "./LoginForm"; +import { MOCK_EMAIL, MOCK_PASSWORD } from "../../api"; + +describe("LoginForm", () => { + afterEach(() => { + useAuthStore.getState().reset(); + vi.restoreAllMocks(); + }); + + it("should render", () => { + render(); + + expect(screen.getByRole("form")).toBeInTheDocument(); + }); + + it("disables submit button when form is invalid", () => { + render(); + expect(screen.getByRole("button", { name: /login/i })).toBeDisabled(); + }); + + it("enables submit button when form is valid", () => { + useAuthStore.getState().setEmail(MOCK_EMAIL); + useAuthStore.getState().setPassword(MOCK_PASSWORD); + render(); + expect(screen.getByRole("button", { name: /login/i })).toBeEnabled(); + }); + + it("updates email when user types", async () => { + render(); + const emailInput = screen.getByRole("textbox", { name: /email/i }); + await userEvent.type(emailInput, MOCK_EMAIL); + expect(selectAuthData(useAuthStore.getState()).email).toBe(MOCK_EMAIL); + }); + + it("updates password when user types", async () => { + render(); + const passwordInput = screen.getByLabelText(/password/i); + await userEvent.type(passwordInput, MOCK_PASSWORD); + expect(selectAuthData(useAuthStore.getState()).password).toBe( + MOCK_PASSWORD, + ); + }); + + it("calls login when submit form is valid and user clicks submit", async () => { + const loginSpy = vi.spyOn(useAuthStore.getState(), "login"); + + useAuthStore.getState().setEmail(MOCK_EMAIL); + useAuthStore.getState().setPassword(MOCK_PASSWORD); + + render(); + const submitButton = screen.getByRole("button", { name: /login/i }); + await userEvent.click(submitButton); + expect(loginSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/features/auth/ui/LoginForm/LoginForm.stories.ts b/src/features/auth/ui/LoginForm/LoginForm.stories.ts new file mode 100644 index 0000000..0cbd906 --- /dev/null +++ b/src/features/auth/ui/LoginForm/LoginForm.stories.ts @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { LoginForm } from "./LoginForm"; + +const meta: Meta = { + component: LoginForm, + title: "features/auth/LoginForm", +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/features/auth/ui/LoginForm/LoginForm.tsx b/src/features/auth/ui/LoginForm/LoginForm.tsx index a33fead..530f02e 100644 --- a/src/features/auth/ui/LoginForm/LoginForm.tsx +++ b/src/features/auth/ui/LoginForm/LoginForm.tsx @@ -1,5 +1,44 @@ +import { selectFormValid, useAuthStore } from "../../model"; +import type { SubmitEvent, ChangeEvent } from "react"; + +/** + * Login form component + */ export function LoginForm() { + const { formData, setEmail, setPassword, login } = useAuthStore(); + + const formValid = useAuthStore(selectFormValid); + + const handleEmailChange = (e: ChangeEvent) => { + setEmail(e.target.value); + }; + + const handlePasswordChange = (e: ChangeEvent) => { + setPassword(e.target.value); + }; + + const handleSubmit = (e: SubmitEvent) => { + e.preventDefault(); + login(); + }; + return ( -
Login Form
+
+ + + +
); }