Skip to content

Commit

Permalink
Merge pull request #48 from Firebird1029/more-tests
Browse files Browse the repository at this point in the history
Added more tests
  • Loading branch information
davidcrair authored Apr 14, 2024
2 parents 472068f + 26c92de commit c8052cf
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ To test the application:
npm test
```

We have achieved 81% statement coverage.
We have achieved 87% statement coverage.

## Linting

Expand Down
130 changes: 130 additions & 0 deletions __tests__/app_page.t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { useRouter } from "next/navigation";
import createClient from "@/utils/supabase/client";
import IndexPage from "../src/app/page";
import loginWithSpotify from "@/app/login/actions";

// Dependency mocks
jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
}));

jest.mock("../src/utils/supabase/client", () => ({
__esModule: true,
default: jest.fn(),
}));

jest.mock("../src/app/login/actions", () => ({
__esModule: true,
default: jest.fn(),
}));

describe("IndexPage", () => {
beforeEach(() => {
useRouter.mockReturnValue({
push: jest.fn(),
});
});

afterEach(() => {
jest.clearAllMocks();
});

test("renders loading state when loggedIn is undefined", () => {
render(<IndexPage />);
expect(screen.getByText("Getting things set up...")).toBeInTheDocument();
});

test("redirects to error page when supabase.auth.getUser fails", async () => {
const mockSupabase = {
auth: {
getUser: jest.fn().mockRejectedValue(new Error("Supabase error")),
},
};
createClient.mockReturnValue(mockSupabase);

render(<IndexPage />);

await waitFor(() => {
expect(useRouter().push).toHaveBeenCalledWith("/error");
});
});

test("sets loggedIn to true when user is already logged in", async () => {
const mockSupabase = {
auth: {
getUser: jest.fn().mockResolvedValue({ data: { user: { id: 1 } } }),
},
};
createClient.mockReturnValue(mockSupabase);

render(<IndexPage />);

await waitFor(() => {
expect(screen.getByText("Continue to Account")).toBeInTheDocument();
});
});

test("redirects to error page on sign-out error", async () => {
global.fetch = jest.fn().mockRejectedValue(new Error("Network error"));

render(<IndexPage />);

fireEvent.click(screen.getByText("Logout"));

await waitFor(() => {
expect(useRouter().push).toHaveBeenCalledWith(
"/error?message=Network error",
);
});
});

test("redirects to the home page on successful sign-out", async () => {
global.fetch = jest.fn().mockResolvedValue({ ok: true });
Object.defineProperty(window, "location", {
value: {
href: "",
},
writable: true,
});

render(<IndexPage />);
fireEvent.click(screen.getByText("Logout"));

await waitFor(() => {
expect(window.location.href).toBe("/");
});
});

test("tests loginWithSpotify on button click", async () => {
// Ensures user is not logged in initially
const mockSupabase = {
auth: {
getUser: jest.fn().mockResolvedValue({ data: { user: null } }),
},
};
createClient.mockReturnValue(mockSupabase);

render(<IndexPage />);

fireEvent.click(screen.getByText("Log in with Spotify"));
expect(loginWithSpotify).toHaveBeenCalled();
});

test("redirects to error page on sign-out failure", async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
statusText: "Forbidden",
});

const router = useRouter();
render(<IndexPage />);

fireEvent.click(screen.getByText("Logout"));

// Wait for the async actions and effects to complete, then check for error
await waitFor(() => {
expect(router.push).toHaveBeenCalledWith("/error?message=Forbidden");
});
});
});
20 changes: 20 additions & 0 deletions __tests__/average_audio_features.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,25 @@ describe("Spotify API functions", () => {
"Error fetching data from Spotify API: Failed to fetch audio features",
);
});

test("throws an error when audio_features is undefined", async () => {
const token = "testToken";
const topSongs = {
items: [
{ id: "id1", popularity: 70 },
{ id: "id2", popularity: 80 },
],
};

const getAudioFeaturesSpy = jest.spyOn(axios, "get");

getAudioFeaturesSpy.mockResolvedValueOnce({
data: { audio_features: undefined },
});

await expect(getAverageAudioFeatures(token, topSongs)).rejects.toThrow(
"Unable to fetch audio features.",
);
});
});
});
61 changes: 59 additions & 2 deletions __tests__/error.t.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
// Tests both error/error.jsx and error/page.jsx

import React from "react";
import { render, screen } from "@testing-library/react";
import ErrorAlert from "@/app/error/error";
import { render, fireEvent, screen } from "@testing-library/react";
import * as nextNavigation from "next/navigation";
import ErrorAlert from "../src/app/error/error";
import ErrorPage from "../src/app/error/page";

// Need to mock window.location as JSDOM does not support doesn't implement it
const assignMock = jest.fn();
delete window.location;
window.location = { assign: assignMock };

jest.mock("next/navigation", () => ({
useSearchParams: jest.fn(),
}));

describe("ErrorAlert", () => {
test("redirects to the specified URL when close button is clicked", () => {
const redirectUrl = "/user";
render(
<ErrorAlert
Title="Error"
Message="An error occurred"
RedirectTo={redirectUrl}
/>,
);

const closeButton = document.querySelector('svg[role="button"]');
fireEvent.click(closeButton);

expect(window.location.href).toBe(redirectUrl);
});
});

describe("ErrorAlert Component Tests", () => {
// Test for proper rendering with props
Expand All @@ -10,3 +41,29 @@ describe("ErrorAlert Component Tests", () => {
expect(screen.getByText("An error has occurred")).toBeInTheDocument();
});
});

describe("ErrorPage", () => {
test("displays default error message when no error query parameter is provided", async () => {
nextNavigation.useSearchParams.mockImplementation(() => {
return new URLSearchParams();
});

const { findByText } = render(<ErrorPage />);
// Need to use await for suspense
const message = await findByText("An error occured.");
expect(message).toBeInTheDocument();
});

test("displays custom error message when error query parameter is provided", async () => {
nextNavigation.useSearchParams.mockImplementation(() => {
const searchParams = new URLSearchParams();
searchParams.set("error", "Custom error message");
return searchParams;
});

const { findByText } = render(<ErrorPage />);
// Need to use await for suspense
const message = await findByText("Custom error message");
expect(message).toBeInTheDocument();
});
});
13 changes: 13 additions & 0 deletions __tests__/layout.t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";
import { render } from "@testing-library/react";
import RootLayout from "../src/app/layout";

describe("RootLayout", () => {
test("RootLayout test", () => {
render(
<RootLayout>
<div>Test</div>
</RootLayout>,
);
});
});
44 changes: 44 additions & 0 deletions __tests__/middleware_src.t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { middleware, config } from "../src/middleware";
import updateSession from "@/utils/supabase/middleware";

jest.mock("../src/utils/supabase/middleware", () => ({
__esModule: true,
default: jest.fn(),
}));

describe("supabase-middleware", () => {
afterEach(() => {
jest.clearAllMocks();
});

describe("middleware", () => {
test("calls updateSession with the request object", async () => {
const request = { some: "request" };
const response = { some: "response" };

updateSession.mockResolvedValueOnce(response);

const result = await middleware(request);

expect(updateSession).toHaveBeenCalledWith(request);
expect(result).toEqual(response);
});

test("throws an error if updateSession throws an error", async () => {
const request = { some: "request" };
const error = new Error("Something went wrong");

updateSession.mockRejectedValueOnce(error);

await expect(middleware(request)).rejects.toThrow(error);
});
});

describe("config", () => {
test("matches the expected paths", () => {
expect(config.matcher).toEqual([
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
]);
});
});
});
10 changes: 10 additions & 0 deletions __tests__/unify.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ describe("featureDataSimilarity", () => {
});
});

describe("featureDataSimilarity", () => {
test("throws an error when the arrays have different lengths", () => {
const features1 = [{ value: 10 }, { value: 30 }, { value: 50 }];
const features2 = [{ value: 10 }, { value: 40 }];
expect(() => featureDataSimilarity(features1, features2)).toThrow(
"Arrays must have the same length",
);
});
});

describe("VinylCircle Component", () => {
test("renders correctly with given props", () => {
const { getByTestId } = render(
Expand Down
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"dotenv": "^16.4.4",
"express": "^4.18.2",
"html-to-image": "^1.11.11",
"isomorphic-fetch": "^3.0.0",
"next": "^14.1.0",
"prop-types": "^15.8.1",
"react": "^18",
Expand Down
4 changes: 0 additions & 4 deletions src/app/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ export default function IndexPage() {
});
}

if (loggedIn === undefined) {
return <div />;
}

return (
<div
className="flex relative \
Expand Down
6 changes: 0 additions & 6 deletions src/app/unify/[users]/UnifyContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,6 @@ function UnifyContent({ user1Data, user2Data }) {
// Create a canvas element
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");

if (!ctx) {
// Unable to obtain 2D context for canvas.
return;
}

canvas.width = img.width;
canvas.height = img.height;

Expand Down

0 comments on commit c8052cf

Please sign in to comment.