feature/login-and-register-forms #2

Merged
ilia merged 12 commits from feature/login-and-register-forms into main 2026-03-24 18:00:42 +00:00
3 changed files with 110 additions and 1 deletions
Showing only changes of commit e4630a7fcb - Show all commits

View File

@@ -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(<LoginForm />);
expect(screen.getByRole("form")).toBeInTheDocument();
});
it("disables submit button when form is invalid", () => {
render(<LoginForm />);
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(<LoginForm />);
expect(screen.getByRole("button", { name: /login/i })).toBeEnabled();
});
it("updates email when user types", async () => {
render(<LoginForm />);
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(<LoginForm />);
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(<LoginForm />);
const submitButton = screen.getByRole("button", { name: /login/i });
await userEvent.click(submitButton);
expect(loginSpy).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,12 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { LoginForm } from "./LoginForm";
const meta: Meta<typeof LoginForm> = {
component: LoginForm,
title: "features/auth/LoginForm",
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -1,5 +1,44 @@
import { selectFormValid, useAuthStore } from "../../model";
import type { SubmitEvent, ChangeEvent } from "react";
/**
* Login form component
*/
export function LoginForm() { export function LoginForm() {
const { formData, setEmail, setPassword, login } = useAuthStore();
const formValid = useAuthStore(selectFormValid);
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
const handleSubmit = (e: SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
login();
};
return ( return (
<form>Login Form</form> <form aria-label="Login form" onSubmit={handleSubmit}>
<input
type="email"
aria-label="Email"
value={formData?.email?.value ?? ""}
onChange={handleEmailChange}
/>
<input
type="password"
aria-label="Password"
value={formData?.password?.value ?? ""}
onChange={handlePasswordChange}
/>
<button type="submit" disabled={!formValid}>
Login
</button>
</form>
); );
} }