mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-28 05:13:57 +03:00
Frontend tests (#426)
* test(useGoogleLogin): add unit tests * test(useMagicLinkLogin): add unit tests * test(usePassword): add unit tests * test(useLogin): add unit tests * test(Login): add unit tests
This commit is contained in:
parent
6f047f4a39
commit
e8b374e8a4
61
frontend/app/(auth)/login/__tests__/page.test.tsx
Normal file
61
frontend/app/(auth)/login/__tests__/page.test.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import Login from "../page";
|
||||||
|
|
||||||
|
const mockUseSearchParams = vi.fn(() => ({
|
||||||
|
get: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockRedirect = vi.fn((url: string) => ({ url }));
|
||||||
|
|
||||||
|
vi.mock("next/navigation", () => ({
|
||||||
|
redirect: (url: string) => mockRedirect(url),
|
||||||
|
useSearchParams: () => mockUseSearchParams(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockUseSupabase = vi.fn(() => ({
|
||||||
|
session: {
|
||||||
|
user: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||||
|
useSupabase: () => mockUseSupabase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/services/analytics/useEventTracking", () => ({
|
||||||
|
useEventTracking: () => ({ track: vi.fn() }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Login component", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("redirects to /upload if user is already signed in and is not coming from another page", () => {
|
||||||
|
render(<Login />);
|
||||||
|
expect(mockRedirect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockRedirect).toHaveBeenCalledWith("/upload");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redirects to "/previous-page" if user is already signed in and previous page is set', () => {
|
||||||
|
const previousPageUrl = "/my-interesting-page";
|
||||||
|
mockUseSearchParams.mockReturnValue({
|
||||||
|
get: vi.fn(() => previousPageUrl),
|
||||||
|
});
|
||||||
|
render(<Login />);
|
||||||
|
expect(mockRedirect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockRedirect).toHaveBeenCalledWith(previousPageUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the login form when user is not signed in", () => {
|
||||||
|
mockUseSupabase.mockReturnValue({
|
||||||
|
//@ts-expect-error doing this for testing purposes
|
||||||
|
session: { user: undefined },
|
||||||
|
});
|
||||||
|
const { getByTestId } = render(<Login />);
|
||||||
|
const signInForm = getByTestId("sign-in-form");
|
||||||
|
expect(signInForm).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { renderHook } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { useGoogleLogin } from "../useGoogleLogin";
|
||||||
|
|
||||||
|
const mockSignInWithOAuth = vi.fn(() => ({ error: null }));
|
||||||
|
|
||||||
|
const mockUseSupabase = () => ({
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
signInWithOAuth: mockSignInWithOAuth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||||
|
useSupabase: () => mockUseSupabase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("useGoogleLogin", () => {
|
||||||
|
it("should call signInWithOAuth", async () => {
|
||||||
|
const { result } = renderHook(() => useGoogleLogin());
|
||||||
|
|
||||||
|
await result.current.signInWithGoogle();
|
||||||
|
|
||||||
|
expect(mockSignInWithOAuth).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
@ -8,7 +8,9 @@ export const useGoogleLogin = () => {
|
|||||||
const { supabase } = useSupabase();
|
const { supabase } = useSupabase();
|
||||||
|
|
||||||
const { publish } = useToast();
|
const { publish } = useToast();
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
const signInWithGoogle = async () => {
|
const signInWithGoogle = async () => {
|
||||||
const { error } = await supabase.auth.signInWithOAuth({
|
const { error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: "google",
|
provider: "google",
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { act, renderHook } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { useMagicLinkLogin } from "../useMagicLinkLogin";
|
||||||
|
|
||||||
|
const mockSignInWithOtp = vi.fn(() => ({ error: null }));
|
||||||
|
|
||||||
|
const mockUseSupabase = () => ({
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
signInWithOtp: mockSignInWithOtp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||||
|
useSupabase: () => mockUseSupabase(),
|
||||||
|
}));
|
||||||
|
const setEmail = vi.fn();
|
||||||
|
|
||||||
|
describe("useMagicLinkLogin", () => {
|
||||||
|
it("should not call signInWithOtp if email is empty", async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useMagicLinkLogin({
|
||||||
|
email: "",
|
||||||
|
setEmail,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await act(() => result.current.handleMagicLinkLogin());
|
||||||
|
expect(mockSignInWithOtp).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call signInWithOtp with proper arguments", async () => {
|
||||||
|
const email = "user@quivr.app";
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useMagicLinkLogin({
|
||||||
|
email,
|
||||||
|
setEmail,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await result.current.handleMagicLinkLogin();
|
||||||
|
expect(mockSignInWithOtp).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockSignInWithOtp).toHaveBeenCalledWith({
|
||||||
|
email,
|
||||||
|
options: { emailRedirectTo: window.location.hostname },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,57 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type UseMagicLinkLoginProps = {
|
||||||
|
email: string;
|
||||||
|
setEmail: (email: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
|
import { useToast } from "@/lib/hooks";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export const useMagicLinkLogin = ({
|
||||||
|
email,
|
||||||
|
setEmail,
|
||||||
|
}: UseMagicLinkLoginProps) => {
|
||||||
|
const { supabase } = useSupabase();
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
const { publish } = useToast();
|
||||||
|
|
||||||
|
const handleMagicLinkLogin = async () => {
|
||||||
|
if (email === "") {
|
||||||
|
publish({
|
||||||
|
variant: "danger",
|
||||||
|
text: "Please enter your email address",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPending(true);
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.signInWithOtp({
|
||||||
|
email,
|
||||||
|
options: {
|
||||||
|
emailRedirectTo: window.location.hostname, // current domain name. for eg localhost:3000, localhost:3001, https://...
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
publish({
|
||||||
|
variant: "danger",
|
||||||
|
text: error.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
publish({
|
||||||
|
variant: "success",
|
||||||
|
text: "Magic link sent successfully if email recognized",
|
||||||
|
});
|
||||||
|
|
||||||
|
setEmail("");
|
||||||
|
}
|
||||||
|
setIsPending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleMagicLinkLogin, isPending };
|
||||||
|
};
|
@ -1,62 +1,28 @@
|
|||||||
/* eslint-disable */
|
|
||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
|
||||||
import { useToast } from "@/lib/hooks/useToast";
|
import { useMagicLinkLogin } from "./hooks/useMagicLinkLogin";
|
||||||
|
|
||||||
type MaginLinkLoginProps = {
|
type MaginLinkLoginProps = {
|
||||||
email: string;
|
email: string;
|
||||||
setEmail: (email: string) => void;
|
setEmail: (email: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MagicLinkLogin = ({ email, setEmail }: MaginLinkLoginProps) => {
|
export const MagicLinkLogin = ({
|
||||||
const { supabase } = useSupabase();
|
email,
|
||||||
const [isPending, setIsPending] = useState(false);
|
setEmail,
|
||||||
|
}: MaginLinkLoginProps): JSX.Element => {
|
||||||
const { publish } = useToast();
|
const { handleMagicLinkLogin, isPending } = useMagicLinkLogin({
|
||||||
|
email,
|
||||||
const handleLogin = async () => {
|
setEmail,
|
||||||
if (email === "") {
|
});
|
||||||
publish({
|
|
||||||
variant: "danger",
|
|
||||||
text: "Please enter your email address",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsPending(true);
|
|
||||||
|
|
||||||
const { error } = await supabase.auth.signInWithOtp({
|
|
||||||
email,
|
|
||||||
options: {
|
|
||||||
emailRedirectTo: window.location.hostname, // current domain name. for eg localhost:3000, localhost:3001, https://...
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
publish({
|
|
||||||
variant: "danger",
|
|
||||||
text: error.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
publish({
|
|
||||||
variant: "success",
|
|
||||||
text: "Magic link sent successfully if email recognized",
|
|
||||||
});
|
|
||||||
|
|
||||||
setEmail("");
|
|
||||||
}
|
|
||||||
setIsPending(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={"tertiary"}
|
variant={"tertiary"}
|
||||||
onClick={handleLogin}
|
onClick={() => void handleMagicLinkLogin()}
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
>
|
>
|
||||||
Send Magic Link
|
Send Magic Link
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
import { act, renderHook } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { usePasswordForgotten } from "../usePasswordForgotten";
|
||||||
|
|
||||||
|
const mockResetPasswordForEmail = vi.fn(() => ({ error: null }));
|
||||||
|
|
||||||
|
const mockUseSupabase = () => ({
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
resetPasswordForEmail: mockResetPasswordForEmail,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||||
|
useSupabase: () => mockUseSupabase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setEmail = vi.fn();
|
||||||
|
|
||||||
|
describe("usePassword", () => {
|
||||||
|
it("should not call resetPasswordForEmail if email is empty", async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePasswordForgotten({
|
||||||
|
email: "",
|
||||||
|
setEmail,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(() => result.current.handleRecoverPassword());
|
||||||
|
expect(mockResetPasswordForEmail).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call resetPasswordForEmail with proper arguments", async () => {
|
||||||
|
const email = "user@quivr.app";
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePasswordForgotten({
|
||||||
|
email,
|
||||||
|
setEmail,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(() => result.current.handleRecoverPassword());
|
||||||
|
expect(mockResetPasswordForEmail).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockResetPasswordForEmail).toHaveBeenCalledWith(email, {
|
||||||
|
redirectTo: `${window.location.origin}/recover-password`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
38
frontend/app/(auth)/login/hooks/__tests__/useLogin.test.ts
Normal file
38
frontend/app/(auth)/login/hooks/__tests__/useLogin.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { act, renderHook } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { useLogin } from "../useLogin";
|
||||||
|
|
||||||
|
const mockSignInWithPassword = vi.fn(() => ({ error: null }));
|
||||||
|
|
||||||
|
const mockUseSupabase = () => ({
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
signInWithPassword: mockSignInWithPassword,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||||
|
useSupabase: () => mockUseSupabase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("useLogin", () => {
|
||||||
|
it("should call signInWithPassword with user email and password", async () => {
|
||||||
|
const { result } = renderHook(() => useLogin());
|
||||||
|
|
||||||
|
const email = "user@quivr.com";
|
||||||
|
const password = "password";
|
||||||
|
|
||||||
|
act(() => result.current.setEmail(email));
|
||||||
|
act(() => result.current.setPassword(password));
|
||||||
|
|
||||||
|
await act(() => result.current.handleLogin());
|
||||||
|
|
||||||
|
expect(mockSignInWithPassword).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockSignInWithPassword).toHaveBeenCalledWith({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
"use client";
|
"use client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import Card from "@/lib/components/ui/Card";
|
import Card from "@/lib/components/ui/Card";
|
||||||
@ -10,8 +10,6 @@ import Field from "@/lib/components/ui/Field";
|
|||||||
import PageHeading from "@/lib/components/ui/PageHeading";
|
import PageHeading from "@/lib/components/ui/PageHeading";
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
|
|
||||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||||
import { GoogleLoginButton } from "./components/GoogleLogin";
|
import { GoogleLoginButton } from "./components/GoogleLogin";
|
||||||
import { MagicLinkLogin } from "./components/MagicLinkLogin";
|
import { MagicLinkLogin } from "./components/MagicLinkLogin";
|
||||||
@ -31,8 +29,9 @@ export default function Login() {
|
|||||||
void track("SIGNED_IN");
|
void track("SIGNED_IN");
|
||||||
if (previousPage === undefined || previousPage === null) {
|
if (previousPage === undefined || previousPage === null) {
|
||||||
redirect("/upload");
|
redirect("/upload");
|
||||||
|
} else {
|
||||||
|
redirect(previousPage);
|
||||||
}
|
}
|
||||||
redirect(previousPage as string);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,6 +40,7 @@ export default function Login() {
|
|||||||
<PageHeading title="Login" subtitle="Welcome back" />
|
<PageHeading title="Login" subtitle="Welcome back" />
|
||||||
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
|
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
|
||||||
<form
|
<form
|
||||||
|
data-testid="sign-in-form"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleLogin();
|
handleLogin();
|
||||||
|
Loading…
Reference in New Issue
Block a user