Compare commits
6 Commits
e4630a7fcb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6751c7fc04 | |||
|
|
83fbf83bae | ||
|
|
576665f32b | ||
|
|
4d854d08a3 | ||
|
|
70bcf7fdcf | ||
|
|
c006a94c4d |
@@ -1,2 +1,3 @@
|
|||||||
export * from "./selectFormValid/selectFormValid";
|
export * from "./selectFormValid/selectFormValid";
|
||||||
export * from "./selectAuthData/selectAuthData";
|
export * from "./selectAuthData/selectAuthData";
|
||||||
|
export * from "./selectStatusIsLoading/selectStatusIsLoading";
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useAuthStore } from "../../stores";
|
||||||
|
import { selectStatusIsLoading } from "./selectStatusIsLoading";
|
||||||
|
|
||||||
|
describe("selectStatusIsLoading", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
useAuthStore.getState().reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when status is 'loading'", () => {
|
||||||
|
useAuthStore.setState({ status: "loading" });
|
||||||
|
expect(selectStatusIsLoading(useAuthStore.getState())).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when status is not 'loading'", () => {
|
||||||
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
expect(selectStatusIsLoading(useAuthStore.getState())).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import type { AuthStore } from "../../types/store";
|
||||||
|
|
||||||
|
export const selectStatusIsLoading = (state: AuthStore) =>
|
||||||
|
state.status === "loading";
|
||||||
@@ -10,49 +10,80 @@ describe("LoginForm", () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render", () => {
|
it("should render form", () => {
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
|
const form = screen.getByRole("form");
|
||||||
|
|
||||||
expect(screen.getByRole("form")).toBeInTheDocument();
|
expect(form).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disables submit button when form is invalid", () => {
|
it("disables submit button when form is invalid", () => {
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
expect(screen.getByRole("button", { name: /login/i })).toBeDisabled();
|
const loginButton = screen.getByRole("button", { name: /login/i });
|
||||||
|
|
||||||
|
expect(loginButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("enables submit button when form is valid", () => {
|
it("disables submit button when auth store status equals loading", async () => {
|
||||||
useAuthStore.getState().setEmail(MOCK_EMAIL);
|
useAuthStore.setState({ status: "loading" });
|
||||||
useAuthStore.getState().setPassword(MOCK_PASSWORD);
|
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
expect(screen.getByRole("button", { name: /login/i })).toBeEnabled();
|
|
||||||
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
|
const loginButton = screen.getByRole("button", { name: /login/i });
|
||||||
|
await userEvent.type(emailInput, MOCK_EMAIL);
|
||||||
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
|
|
||||||
|
expect(loginButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates email when user types", async () => {
|
it("enables submit button when form is valid and auth store status isn't equal to loading", async () => {
|
||||||
|
useAuthStore.setState({ status: "idle" });
|
||||||
|
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
|
const loginButton = screen.getByRole("button", { name: /login/i });
|
||||||
|
await userEvent.type(emailInput, MOCK_EMAIL);
|
||||||
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
|
|
||||||
|
expect(loginButton).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates email value in auth store when user types", async () => {
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
await userEvent.type(emailInput, MOCK_EMAIL);
|
await userEvent.type(emailInput, MOCK_EMAIL);
|
||||||
expect(selectAuthData(useAuthStore.getState()).email).toBe(MOCK_EMAIL);
|
|
||||||
|
const storeEmailValue = selectAuthData(useAuthStore.getState()).email;
|
||||||
|
expect(storeEmailValue).toBe(MOCK_EMAIL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates password when user types", async () => {
|
it("updates password value in auth store when user types", async () => {
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
expect(selectAuthData(useAuthStore.getState()).password).toBe(
|
|
||||||
MOCK_PASSWORD,
|
const storePasswordValue = selectAuthData(useAuthStore.getState()).password;
|
||||||
);
|
expect(storePasswordValue).toBe(MOCK_PASSWORD);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls login when submit form is valid and user clicks submit", async () => {
|
it("calls login when submit form is valid and user clicks submit", async () => {
|
||||||
const loginSpy = vi.spyOn(useAuthStore.getState(), "login");
|
const loginSpy = vi.spyOn(useAuthStore.getState(), "login");
|
||||||
|
useAuthStore.setState({ status: "idle" });
|
||||||
useAuthStore.getState().setEmail(MOCK_EMAIL);
|
|
||||||
useAuthStore.getState().setPassword(MOCK_PASSWORD);
|
|
||||||
|
|
||||||
render(<LoginForm />);
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
const emailInput = screen.getByRole("textbox", { name: /email/i });
|
||||||
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
const submitButton = screen.getByRole("button", { name: /login/i });
|
const submitButton = screen.getByRole("button", { name: /login/i });
|
||||||
|
|
||||||
|
await userEvent.type(emailInput, MOCK_EMAIL);
|
||||||
|
await userEvent.type(passwordInput, MOCK_PASSWORD);
|
||||||
await userEvent.click(submitButton);
|
await userEvent.click(submitButton);
|
||||||
|
|
||||||
expect(loginSpy).toHaveBeenCalled();
|
expect(loginSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { selectFormValid, useAuthStore } from "../../model";
|
import {
|
||||||
|
selectFormValid,
|
||||||
|
selectStatusIsLoading,
|
||||||
|
useAuthStore,
|
||||||
|
} from "../../model";
|
||||||
import type { SubmitEvent, ChangeEvent } from "react";
|
import type { SubmitEvent, ChangeEvent } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,6 +12,9 @@ export function LoginForm() {
|
|||||||
const { formData, setEmail, setPassword, login } = useAuthStore();
|
const { formData, setEmail, setPassword, login } = useAuthStore();
|
||||||
|
|
||||||
const formValid = useAuthStore(selectFormValid);
|
const formValid = useAuthStore(selectFormValid);
|
||||||
|
const isLoading = useAuthStore(selectStatusIsLoading);
|
||||||
|
|
||||||
|
const disabled = !formValid || isLoading;
|
||||||
|
|
||||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
@@ -36,7 +43,7 @@ export function LoginForm() {
|
|||||||
value={formData?.password?.value ?? ""}
|
value={formData?.password?.value ?? ""}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
/>
|
/>
|
||||||
<button type="submit" disabled={!formValid}>
|
<button type="submit" disabled={disabled}>
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
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() {
|
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 (
|
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