Skip to content

Commit

Permalink
Merge pull request #9 from atlp-rwanda/ft-reset-password-#187419121
Browse files Browse the repository at this point in the history
#187419121 user should reset a password using email link
  • Loading branch information
teerenzo authored Jun 25, 2024
2 parents e35d4b3 + 4754038 commit da31e7d
Show file tree
Hide file tree
Showing 18 changed files with 675 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from "react";
import "./App.css";
import "react-toastify/dist/ReactToastify.css";

import AppRoutes from "./routes/AppRoutes";
import 'react-toastify/dist/ReactToastify.css';

const App: React.FC = () => (
<main>
Expand Down
136 changes: 136 additions & 0 deletions src/__test__/getLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import "@testing-library/jest-dom";
import {
render, screen, fireEvent, waitFor,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";

import store from "../redux/store";
import GetLinkPage from "../pages/GetLinkPage";
import { getLink } from "../redux/reducers/getLinkSlice";
import axios from "../redux/api/api";

jest.mock("../redux/api/api");
jest.mock("react-toastify", () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
},
ToastContainer: jest.fn(),
}));

describe("GetLinkPage and getLinkSlice", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("should render GetLinkPage correctly", () => {
render(
<Provider store={store}>
<Router>
<GetLinkPage />
</Router>
</Provider>,
);

expect(screen.getByText("eagles", { exact: false })).toBeInTheDocument();
expect(
screen.getByText("Get a link to reset password"),
).toBeInTheDocument();
expect(screen.getByPlaceholderText("Email")).toBeInTheDocument();
expect(screen.getByText("Send Link")).toBeInTheDocument();
});

test("should handle initial state", () => {
expect(store.getState().getLink).toEqual({
isLoading: false,
data: [],
error: null,
});
});

test("should handle getLink.pending", () => {
// @ts-ignore
store.dispatch(getLink.pending());
expect(store.getState().getLink).toEqual({
isLoading: true,
data: [],
error: null,
});
});

test("should handle getLink.fulfilled", () => {
const mockData = { message: "Reset link sent successfully" };
store.dispatch(
getLink.fulfilled(mockData, "", { email: "[email protected]" }),
);
expect(store.getState().getLink).toEqual({
isLoading: false,
data: mockData,
error: null,
});
});

test("should handle form submission", async () => {
const mockData = { message: "Reset link sent successfully" };
(axios.post as jest.Mock).mockResolvedValueOnce({ data: mockData });

render(
<Provider store={store}>
<Router>
<GetLinkPage />
</Router>
</Provider>,
);

const emailInput = screen.getByPlaceholderText("Email");
const submitButton = screen.getByText("Send Link");

userEvent.type(emailInput, "[email protected]");
fireEvent.click(submitButton);

await waitFor(() => {
expect(store.getState().getLink.isLoading).toBe(false);
expect(store.getState().getLink.data).toEqual(mockData);
});
});

test("should show success message after successful submission", async () => {
const mockData = { message: "Reset link sent successfully" };
(axios.post as jest.Mock).mockResolvedValueOnce({ data: mockData });

render(
<Provider store={store}>
<Router>
<GetLinkPage />
</Router>
</Provider>,
);

const emailInput = screen.getByPlaceholderText("Email");
const submitButton = screen.getByText("Send Link");

userEvent.type(emailInput, "[email protected]");
fireEvent.click(submitButton);

await waitFor(() => {
expect(store.getState().getLink.data).toEqual(mockData);
});
});

test("getLink thunk should make API call and handle success", async () => {
const mockData = { message: "Reset link sent successfully" };
(axios.post as jest.Mock).mockResolvedValueOnce({ data: mockData });

const dispatch = jest.fn();
const thunk = getLink({ email: "[email protected]" });

await thunk(dispatch, () => ({}), undefined);

const { calls } = dispatch.mock;
expect(calls[0][0].type).toBe("getLinkEmail/pending");
expect(calls[1][0].type).toBe("getLinkEmail/fulfilled");
expect(calls[1][0].payload).toEqual(mockData);
});
});
71 changes: 62 additions & 9 deletions src/__test__/registerUser.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// @ts-nocheck
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import userEvent from "@testing-library/user-event";
import { AnyAction } from "redux";

import store from "../redux/store";
import RegisterUser from "../pages/RegisterUser";
import { createUser } from "../redux/reducers/registerSlice";

test("should render registration page correctly", async () => {
render(
Expand All @@ -14,13 +18,62 @@ test("should render registration page correctly", async () => {
</Router>
</Provider>,
);
const name = screen.getAllByPlaceholderText("Name");
const username = screen.getAllByPlaceholderText("Username");
const email = screen.getAllByPlaceholderText("Email");
const password = screen.getAllByPlaceholderText("Password");

expect(name).toBeTruthy();
expect(username).toBeTruthy();
expect(email).toBeTruthy();
expect(password).toBeTruthy();

const name = screen.getByPlaceholderText("Name");
const username = screen.getByPlaceholderText("Username");
const email = screen.getByPlaceholderText("Email");
const password = screen.getByPlaceholderText("Password");

expect(name).toBeInTheDocument();
expect(username).toBeInTheDocument();
expect(email).toBeInTheDocument();
expect(password).toBeInTheDocument();

const googleButton = screen.getByRole("button", {
name: /Sign up with Google/i,
});
expect(googleButton).toBeInTheDocument();

const loginLink = screen.getByRole("link", { name: /Login/i });
expect(loginLink).toBeInTheDocument();
expect(loginLink.getAttribute("href")).toBe("/login");

userEvent.click(loginLink);
});

it("should handle initial state", () => {
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: null,
});
});

it("should handle registerUser.pending", () => {
// @ts-ignore
store.dispatch(createUser.pending(""));
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: null,
});
});

it("should handle createUser.fulfilled", () => {
const mockData = { message: "Account created successfully!" };
store.dispatch(createUser.fulfilled(mockData, "", {} as AnyAction));
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: null,
});
});

it("should handle createUser.rejected", () => {
store.dispatch(createUser.rejected(null, "", {} as AnyAction));
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: null,
});
});
85 changes: 85 additions & 0 deletions src/__test__/resetPassword.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// @ts-nocheck
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import { AnyAction } from "@reduxjs/toolkit";

import store from "../redux/store";
import ResetPassword from "../pages/ResetPassword";
import { resetPassword } from "../redux/reducers/resetPasswordSlice";

test("should render reset password page correctly", async () => {
render(
<Provider store={store}>
<Router>
<ResetPassword />
</Router>
</Provider>,
);

expect(screen.getByText("eagles", { exact: false })).toBeInTheDocument();
expect(
screen.getByText("Reset your password", { exact: false }),
).toBeInTheDocument();
const description = screen.getAllByText(
"Before you write your password consider if your password is strong enough that can not be guessed or cracked by anyone.",
);
const newPassword = screen.getAllByPlaceholderText("New Password");
const confirmPassword = screen.getAllByPlaceholderText("Confirm password");

expect(description).toBeTruthy();
expect(newPassword).toBeTruthy();
expect(confirmPassword).toBeTruthy();
});

test("the link is not disabled", () => {
const { getByText } = render(
<Provider store={store}>
<Router>
<ResetPassword />
</Router>
</Provider>,
);
const linkElement = getByText("Reset");
expect(linkElement).not.toBeDisabled();
expect(linkElement).toBeEnabled();
expect(linkElement).toBeVisible();
});

it("should handle initial state", () => {
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: null,
});
});

it("should handle resetPassword.pending", () => {
// @ts-ignore
store.dispatch(resetPassword.pending(""));
expect(store.getState().reset).toEqual({
isLoading: true,
data: [],
error: null,
});
});

it("should handle resetPassword.fulfilled", () => {
const mockData = { message: "Password reset successfully" };
store.dispatch(resetPassword.fulfilled(mockData, "", {} as AnyAction));
expect(store.getState().reset).toEqual({
isLoading: false,
data: mockData,
error: null,
});
});

it("should handle resetPassword.rejected", () => {
store.dispatch(resetPassword.rejected(null, "", {} as AnyAction));
expect(store.getState().reset).toEqual({
isLoading: false,
data: [],
error: undefined,
});
});
10 changes: 8 additions & 2 deletions src/components/common/auth/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ interface ButtonProps {
text: string;
disabled?: boolean;
dataTestId?: string;
backgroundColor?: string;
}

const Button: React.FC<ButtonProps> = ({ text, disabled, dataTestId }) => (
const Button: React.FC<ButtonProps> = ({
text,
disabled,
dataTestId,
backgroundColor,
}) => (
<button
type="submit"
className="bg-[#161616] text-white py-3 my-4 text-normal md:text-lg rounded-sm"
className={`${backgroundColor} text-white py-3 px-12 my-4 text-normal md:text-lg rounded-sm cursor-pointer`}
disabled={disabled}
data-testid={dataTestId}
>
Expand Down
12 changes: 12 additions & 0 deletions src/components/common/auth/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FaCircle } from "react-icons/fa";

const Logo = () => (
<div className="text-black font-bold text-[30px] flex items-center gap-1 p-5">
<h1 className="font-medium text-[36px]">
<span className="font-[550] text-heading"> eagles</span>
</h1>
<FaCircle className="text-sm text-[#DB4444] mt-3" />
</div>
);

export default Logo;
10 changes: 10 additions & 0 deletions src/components/common/auth/createSlice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createSlice } from "@reduxjs/toolkit";
// @ts-ignore
const createCustomSlice = (name, initialState, extraReducers) => createSlice({
name,
initialState,
reducers: {},
extraReducers,
});

export default createCustomSlice;
Loading

0 comments on commit da31e7d

Please sign in to comment.