Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tests to Pass Coverage #29

Merged
merged 5 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/api/category.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getCategories } from "./category";

it("should return an array of categories", async function () {
const categories = await getCategories();
expect(categories).toBeInstanceOf(Array);
expect(categories.length).toBeGreaterThan(0);
});
84 changes: 78 additions & 6 deletions src/components/authentication/SignInForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,38 @@ import userEvent from "@testing-library/user-event";

import SignInForm from "./SignInForm";

const mockedMethods = vi.hoisted(function () {
const mocks = vi.hoisted(function () {
const userCredentials = { id: "123" };
return {
userCredentials,
getRedirectResultFn: vi.fn().mockResolvedValue({ userCredentials }),
signInAuthUserFn: vi.fn(),
signInWithGooglePopupFn: vi.fn().mockResolvedValue({ userCredentials }),
createUserDocumentFromAuthFn: vi.fn(),
signInWithGoogleRedirectFn: vi.fn(),
};
});

vi.mock("@/utils/firebase", function () {
vi.mock("firebase/auth", async function () {
return { getRedirectResult: mocks.getRedirectResultFn };
});

vi.mock("@/utils/firebase", async function () {
return {
signInAuthUserWithEmailAndPassword: mockedMethods.signInAuthUserFn,
auth: {},
createUserDocumentFromAuth: mocks.createUserDocumentFromAuthFn,
signInAuthUserWithEmailAndPassword: mocks.signInAuthUserFn,
signInWithGooglePopup: mocks.signInWithGooglePopupFn,
signInWithGoogleRedirect: mocks.signInWithGoogleRedirectFn,
};
});

vi.spyOn(window, "alert").mockImplementation(() => {});

beforeEach(() => {
vi.clearAllMocks();
});

test("should render the correct titles", function () {
render(<SignInForm />);

Expand Down Expand Up @@ -55,14 +73,14 @@ test("should submit the form with the correct data", async function () {
await user.type(passwordInput, "password");
await user.click(submitButton);

expect(mockedMethods.signInAuthUserFn).toHaveBeenCalledWith(
expect(mocks.signInAuthUserFn).toHaveBeenCalledWith(
"[email protected]",
"password",
);
});

test("should show an alert if the password is incorrect", async function () {
mockedMethods.signInAuthUserFn.mockRejectedValue({
mocks.signInAuthUserFn.mockRejectedValue({
code: "auth/wrong-password",
});

Expand All @@ -82,7 +100,7 @@ test("should show an alert if the password is incorrect", async function () {
});

test("should show an alert if the user is not found", async function () {
mockedMethods.signInAuthUserFn.mockRejectedValue({
mocks.signInAuthUserFn.mockRejectedValue({
code: "auth/user-not-found",
});

Expand All @@ -100,3 +118,57 @@ test("should show an alert if the user is not found", async function () {

expect(window.alert).toHaveBeenCalledWith("Wrong email or password");
});

test("should log an error if signing in fails", async function () {
const consoleSpy = vi.spyOn(console, "error");
mocks.signInAuthUserFn.mockRejectedValue(new Error("Failed to sign in"));

render(<SignInForm />);

const emailInput = screen.getByLabelText(/email/i);
const passwordInput = screen.getByLabelText(/password/i);
const submitButton = screen.getByRole("button", { name: /^sign in$/i });

const user = userEvent.setup();

await user.type(emailInput, "[email protected]");
await user.type(passwordInput, "password");
await user.click(submitButton);

expect(consoleSpy).toHaveBeenCalledWith(
"Error signing in",
expect.any(Error),
);
});

test("should sign in with Google using popup successfully", async function () {
render(<SignInForm />);

await userEvent.click(
screen.getByRole("button", {
name: /google sign in/i,
}),
);

expect(mocks.signInAuthUserFn).not.toHaveBeenCalled();
expect(mocks.signInWithGooglePopupFn).toHaveBeenCalled();
expect(mocks.createUserDocumentFromAuthFn).not.toHaveBeenCalledWith(
mocks.userCredentials,
);
});

test("should sign in with Google redirect successfully", async function () {
render(<SignInForm useRedirect />);

await userEvent.click(
screen.getByRole("button", {
name: /google sign in/i,
}),
);

expect(mocks.signInAuthUserFn).not.toHaveBeenCalled();
expect(mocks.signInWithGoogleRedirectFn).toHaveBeenCalled();
expect(mocks.createUserDocumentFromAuthFn).not.toHaveBeenCalledWith(
mocks.userCredentials,
);
});
13 changes: 6 additions & 7 deletions src/components/authentication/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function SignInForm({ useRedirect = false }: SignInFormProps) {
(error as AuthError).code === "auth/wrong-password" ||
(error as AuthError).code === "auth/user-not-found"
) {
alert("Wrong email or password");
return alert("Wrong email or password");
}
console.error("Error signing in", error);
}
Expand All @@ -66,6 +66,10 @@ export default function SignInForm({ useRedirect = false }: SignInFormProps) {
await signInWithGoogleRedirect();
}

const handleGoogleSignin = !useRedirect
? handleLoginWithGooglePopup
: handleRedirectWithGoogle;

return (
<section className="flex w-96 flex-col">
<span className="italic text-gray-600">Already have an account?</span>
Expand Down Expand Up @@ -94,15 +98,10 @@ export default function SignInForm({ useRedirect = false }: SignInFormProps) {
<Button
type="button"
buttonType="google-sign-in"
onClick={handleLoginWithGooglePopup}
onClick={handleGoogleSignin}
>
Google Sign In
</Button>
{useRedirect && (
<Button type="button" onClick={handleRedirectWithGoogle}>
Google Sign In
</Button>
)}
</div>
</form>
</section>
Expand Down
28 changes: 28 additions & 0 deletions src/components/authentication/SignUpForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,31 @@ test("should show an alert if email is already in use", async function () {
"Cannot create user, email already in use!",
);
});

test("should log an error if sign up fails", async function () {
const consoleSpy = vi.spyOn(console, "error");
mockedMethods.createAuthUserFn.mockRejectedValue(
new Error("Failed to sign up"),
);

render(<SignUpForm />);

const displayNameInput = screen.getByLabelText(/display name/i);
const emailInput = screen.getByLabelText(/email/i);
const passwordInput = screen.getByLabelText(/^password/i);
const confirmPasswordInput = screen.getByLabelText(/^confirm password/i);
const submitButton = screen.getByRole("button", { name: /sign up/i });

const user = userEvent.setup();

await user.type(displayNameInput, "John Doe");
await user.type(emailInput, "[email protected]");
await user.type(passwordInput, "password");
await user.type(confirmPasswordInput, "password");
await user.click(submitButton);

expect(consoleSpy).toHaveBeenCalledWith(
"Error signing up",
expect.any(Error),
);
});
102 changes: 102 additions & 0 deletions src/utils/firebase.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signInWithPopup,
signInWithRedirect,
User,
} from "firebase/auth";
import {
auth,
createAuthUserWithEmailAndPassword,
createUserDocumentFromAuth,
signInAuthUserWithEmailAndPassword,
signInWithGooglePopup,
signInWithGoogleRedirect,
} from "./firebase";

const existsFn = vi.fn().mockReturnValue(false);
const { setDocSpy } = vi.hoisted(function () {
return { setDocSpy: vi.fn() };
});

vi.mock("firebase/auth");
vi.mock("firebase/firestore", async function () {
const firestore = await vi.importActual("firebase/firestore");
return {
...firestore,
doc: () => ({ id: "123" }),
getDoc: () => ({ exists: existsFn }),
setDoc: setDocSpy,
};
});

const mockUserAuth = {
uid: "123",
displayName: "Test User",
email: "[email protected]",
} as User;

beforeEach(() => {
vi.clearAllMocks();
});

describe("signInWithGooglePopup", function () {
it("should sign in with Google using popup", async () => {
await signInWithGooglePopup();
expect(signInWithPopup).toHaveBeenCalledWith(auth, expect.any(Object));
});
});

describe("signInWithGoogleRedirect", function () {
it("should sign in with Google using redirect", async () => {
await signInWithGoogleRedirect();
expect(signInWithRedirect).toHaveBeenCalledWith(auth, expect.any(Object));
});
});

describe("createUserDocumentFromAuth", function () {
it("should create a user document from auth if user doesn't exist", async () => {
const userDocRef = await createUserDocumentFromAuth(mockUserAuth);
expect(setDocSpy).toHaveBeenCalledWith(userDocRef, expect.any(Object));
expect(userDocRef.id).toBe("123");
});

it("should just return the user document reference if user exists", async () => {
existsFn.mockReturnValueOnce(true);
const userDocRef = await createUserDocumentFromAuth(mockUserAuth);
expect(setDocSpy).not.toHaveBeenCalled();
expect(userDocRef.id).toBe("123");
});

it("should log an error if creating a user fails", async function () {
const consoleSpy = vi.spyOn(console, "error");
setDocSpy.mockRejectedValue(new Error("Failed to create user document"));
await createUserDocumentFromAuth(mockUserAuth);
expect(consoleSpy).toHaveBeenCalledWith(
"Error creating user document",
expect.any(Error),
);
});
});

describe("createAuthUserWithEmailAndPassword", function () {
it("should create an auth user with email and password successfully", async function () {
await createAuthUserWithEmailAndPassword("[email protected]", "password");
expect(createUserWithEmailAndPassword).toHaveBeenCalledWith(
auth,
"[email protected]",
"password",
);
});
});

describe("signInAuthUserWithEmailAndPassword", function () {
it("should sign in an auth user with email and password successfully", async function () {
await signInAuthUserWithEmailAndPassword("[email protected]", "password");
expect(signInWithEmailAndPassword).toHaveBeenCalledWith(
auth,
"[email protected]",
"password",
);
});
});
1 change: 1 addition & 0 deletions src/utils/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ if (FIREBASE_AUTH_EMULATOR) {

export const signInWithGooglePopup = () =>
signInWithPopup(auth, googleAuthProvider);

export const signInWithGoogleRedirect = () =>
signInWithRedirect(auth, googleAuthProvider);

Expand Down
1 change: 1 addition & 0 deletions tests/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const coverageV8Options: CoverageV8Options = {
reporter: ["html", "text"],
reportsDirectory: "./tests/coverage",
thresholds: { statements: 90, branches: 90, functions: 90, lines: 90 },
exclude: ["**/*.d.ts", "src/*.tsx", "src/models/*"],
};
4 changes: 3 additions & 1 deletion tests/setup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as matchers from "@testing-library/jest-dom/matchers";
import { cleanup } from "@testing-library/react";
import { afterEach, expect } from "vitest";
import { afterEach, expect, vi } from "vitest";

expect.extend(matchers);

vi.mock("@/config/firebase");

afterEach(() => {
cleanup();
});
Loading