feat(RegisterForm): add basic RegisterFormComponent with test coverage and storybook placeholder
This commit is contained in:
115
src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx
Normal file
115
src/features/auth/ui/RegisterForm/RegisterForm.spec.tsx
Normal file
@@ -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(<RegisterForm />);
|
||||
|
||||
const form = screen.getByRole("form");
|
||||
expect(form).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should disable button when form is invalid", async () => {
|
||||
render(<RegisterForm />);
|
||||
|
||||
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(<RegisterForm />);
|
||||
|
||||
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(<RegisterForm />);
|
||||
|
||||
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(<RegisterForm />);
|
||||
|
||||
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(<RegisterForm />);
|
||||
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(<RegisterForm />);
|
||||
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(<RegisterForm />);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
12
src/features/auth/ui/RegisterForm/RegisterForm.stories.ts
Normal file
12
src/features/auth/ui/RegisterForm/RegisterForm.stories.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { RegisterForm } from "./RegisterForm";
|
||||
|
||||
const meta: Meta<typeof RegisterForm> = {
|
||||
component: RegisterForm,
|
||||
title: "features/auth/RegisterForm",
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@@ -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<HTMLInputElement>) => {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
|
||||
const handleConfirmChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setConfirmedPassword(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
register();
|
||||
};
|
||||
|
||||
return (
|
||||
<form>Register Form</form>
|
||||
<form aria-label="Register 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}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
aria-label="Confirm"
|
||||
value={confirmedPassword}
|
||||
onChange={handleConfirmChange}
|
||||
/>
|
||||
<button type="submit" aria-label="Register" disabled={disabled}>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user