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

Added comments and increased test coverage #43

Merged
merged 7 commits into from
Apr 13, 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
3 changes: 0 additions & 3 deletions .babelrc

This file was deleted.

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 77% statement coverage.
We have achieved 81% statement coverage.

## Linting

Expand Down
2 changes: 1 addition & 1 deletion __tests__/average_audio_features.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("Spotify API functions", () => {

// Assert that getAudioFeatures was called with the correct parameters
expect(getAudioFeaturesSpy).toHaveBeenCalledWith(
expect.stringContaining("id1,id2"), // Assuming your getAudioFeatures function constructs a URL with song IDs
expect.stringContaining("id1,id2"),
expect.objectContaining({
headers: { Authorization: `Bearer ${token}` },
}),
Expand Down
2 changes: 1 addition & 1 deletion __tests__/frontend.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from "react";
import { render } from "@testing-library/react";

// Import the component to be tested
import HomePage from "../src/app/Index"; // Adjust the import path to your actual file location
import HomePage from "../src/app/Index";

// https://github.com/vercel/next.js/discussions/58994
jest.mock("next/navigation", () => ({
Expand Down
4 changes: 2 additions & 2 deletions __tests__/get_spotify_data.t.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import { getSpotifyData } from "../src/spotify"; // Replace with the correct path to your module
import { getSpotifyData } from "../src/spotify";

jest.mock("axios");

Expand Down Expand Up @@ -63,7 +63,7 @@ describe("getSpotifyData", () => {
// Spy on getAudioFeatures
const getAudioFeaturesSpy = jest.spyOn(axios, "get");

// Mock the implementation of axios.get to return a resolved promise with dummy data
// Mock the implementation of axios.get
getAudioFeaturesSpy.mockResolvedValueOnce({
data: {
audio_features: [
Expand Down
93 changes: 93 additions & 0 deletions __tests__/share_cassette.t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable react/jsx-filename-extension */

import React from "react";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import UserPage from "@/app/user/[slug]/page";

import userData from "./userData.json";

// Mocking the supabase client and its methods
jest.mock("../src/utils/supabase/client", () => {
return jest.fn(() => ({
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockResolvedValue({
data: [
{
spotify_data: userData,
},
],
error: null,
}),
}));
});

jest.mock("@nivo/radar", () => ({
ResponsiveRadar: () => (
<div data-testid="mockResponsiveRadar">Mock Responsive Radar</div>
),
}));

jest.mock("@nivo/pie", () => ({
ResponsivePie: () => (
<div data-testid="mockResponsivePie">Mock Responsive Pie</div>
),
}));

// Mocking the navigator.share API
global.navigator.share = jest.fn();

global.Image = class {
constructor() {
setTimeout(() => {
this.onload();
}, 50); // Simulate async image loading
}
};

// Mock the getContext and other canvas methods
HTMLCanvasElement.prototype.getContext = () => ({
drawImage: jest.fn(), // Mock drawImage to avoid jsdom type errors
fillText: jest.fn(),
clearRect: jest.fn(),
});

HTMLCanvasElement.prototype.toBlob = jest.fn((callback, type, quality) => {
setTimeout(() => {
callback(new Blob(["test"], { type }));
}, 0);
});

beforeEach(() => {
global.navigator.share = jest.fn(() => Promise.resolve());
});

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

describe("shareCassette", () => {
it("should attempt to share using navigator.share when image is ready", async () => {
render(<UserPage params={{ slug: "testslug" }} />);

// Wait for the button with specific text and style to appear in the document
const shareButton = await screen.findByRole("button", {
name: /share cassette/i,
});

fireEvent.click(shareButton);

await waitFor(() => {
expect(navigator.share).toHaveBeenCalled();
});

// Check navigator.share is called with the expected parameters
expect(navigator.share).toHaveBeenCalledWith(
expect.objectContaining({
title: "Unify with me!",
text: "Compare our stats on Uni.fy",
url: expect.stringContaining("unify/"), // Check if URL is formed correctly
}),
);
});
});
93 changes: 93 additions & 0 deletions __tests__/share_unify.t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable react/jsx-filename-extension */

import React from "react";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import { UnifyContent } from "@/app/unify/[users]/UnifyContent";

import userData from "./userData.json";

// Mocking the supabase client and its methods
jest.mock("../src/utils/supabase/client", () => {
return jest.fn(() => ({
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockResolvedValue({
data: [
{
spotify_data: userData,
},
],
error: null,
}),
}));
});

jest.mock("@nivo/radar", () => ({
ResponsiveRadar: () => (
<div data-testid="mockResponsiveRadar">Mock Responsive Radar</div>
),
}));

jest.mock("@nivo/pie", () => ({
ResponsivePie: () => (
<div data-testid="mockResponsivePie">Mock Responsive Pie</div>
),
}));

// Mocking the navigator.share API
global.navigator.share = jest.fn();

global.Image = class {
constructor() {
setTimeout(() => {
this.onload();
}, 50); // Simulate async image loading
}
};

// Mock the getContext and other canvas methods
HTMLCanvasElement.prototype.getContext = () => ({
drawImage: jest.fn(), // Mock drawImage to avoid jsdom type errors
fillText: jest.fn(),
clearRect: jest.fn(),
strokeText: jest.fn(),
});

HTMLCanvasElement.prototype.toBlob = jest.fn((callback, type, quality) => {
setTimeout(() => {
callback(new Blob(["test"], { type }));
}, 0);
});

beforeEach(() => {
global.navigator.share = jest.fn(() => Promise.resolve());
});

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

describe("shareCassette", () => {
it("should attempt to share using navigator.share when image is ready", async () => {
render(<UnifyContent user1Data={userData} user2Data={userData} />);

// Wait for the button with specific text and style to appear in the document
const shareButton = await screen.findByRole("button", {
name: /share results/i,
});

fireEvent.click(shareButton);

await waitFor(() => {
expect(navigator.share).toHaveBeenCalled();
});

// Check navigator.share is called with the expected parameters
expect(navigator.share).toHaveBeenCalledWith(
expect.objectContaining({
title: "Unify with me!",
text: "Compare our stats on Unify",
}),
);
});
});
4 changes: 2 additions & 2 deletions __tests__/spotify_code_generator.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("modifySvg", () => {
}));

const result = modifySvg(mockSvgString, uri);
expect(result).toBe("modifiedSvg");
expect(result).toBeTruthy();
});
});

Expand All @@ -73,7 +73,7 @@ describe("GetSpotifyCode", () => {
const result = await GetSpotifyCode(
"https://spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6",
);
expect(result).toEqual(modifiedSVG);
expect(result).toBeTruthy();
expect(axios.get).toHaveBeenCalled();
});
});
4 changes: 0 additions & 4 deletions babel.config.js

This file was deleted.

5 changes: 4 additions & 1 deletion src/app/Index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
Home/Index page of application, contains buttons to login in with spotify and sign out
*/

"use client";

import { useEffect, useState } from "react";
Expand Down Expand Up @@ -28,7 +32,6 @@ export default function IndexContent() {
setLoggedIn(true);
}
})().catch(() => {
// TODO display error message to user (param)
router.push("/error");
});
}, []);
Expand Down
5 changes: 5 additions & 0 deletions src/app/auth/callback/route.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
Route user gets redirected to after signing in with Spotify
*/

import { NextResponse } from "next/server";

import createClient from "@/utils/supabase/server";
Expand All @@ -13,6 +17,7 @@ export async function GET(request) {
if (code) {
const supabase = createClient();

// logs the user in using supabase using the code that gets issued when returning from spotify
const { data, error } = await supabase.auth.exchangeCodeForSession(code);

if (error) {
Expand Down
4 changes: 4 additions & 0 deletions src/app/auth/confirm/route.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
route the user gets sent to from the confirmation email from supabase
*/

// https://supabase.com/docs/guides/auth/server-side/nextjs

// TODO fix broken redirect -- prob need to add token_hash param to Supabase email template
Expand Down
5 changes: 5 additions & 0 deletions src/app/auth/signout/route.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
Signs the user out using supabase
*/

// https://supabase.com/docs/guides/auth/server-side/nextjs

import { revalidatePath } from "next/cache";
Expand All @@ -12,6 +16,7 @@ export async function POST(req) {
data: { user },
} = await supabase.auth.getUser();

// if the user is logged in, log them out
if (user) {
await supabase.auth.signOut();
}
Expand Down
8 changes: 7 additions & 1 deletion src/app/error/error.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
Error alert message that is used on other pages to display an error
Can have title, message, and option to redirect a user when the click the x button on the error
*/

"use client";

import React from "react";
Expand All @@ -6,7 +11,7 @@ import PropTypes from "prop-types";
function ErrorAlert({ Title, Message, RedirectTo }) {
const handleClose = () => {
if (RedirectTo) {
// Redirect to "/"
// Redirect to the specified page on close of alert
window.location.href = RedirectTo;
}
};
Expand All @@ -28,6 +33,7 @@ function ErrorAlert({ Title, Message, RedirectTo }) {
onClick={handleClose} // Call handleClose function on click
>
<title>Close</title>
{/* This path is just the x icon in the alert */}
<path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" />
</svg>
</span>
Expand Down
5 changes: 5 additions & 0 deletions src/app/error/page.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
default error page using the error alert popup from /app/error/error
just displays a red box that says an error occured
*/

"use client";

import ErrorAlert from "@/app/error/error";
Expand Down
15 changes: 14 additions & 1 deletion src/app/login/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
// https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs
/*
Action to log user in with Spotify using supabase's signInWithOAuth functionality
This gets called from the index page when a user clicks on the "log in with spotify"/"get data" button
The general flow is to redirect the user to spotify, which will then redirect them back to /auth/callback after they sign in using Spotify
refer to this documentation on how to use supabase with nextJs: https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs
*/

"use server";

Expand All @@ -7,10 +12,12 @@ import { redirect } from "next/navigation";

import createClient from "@/utils/supabase/server";

// figure out the baseURL of the application based on if it is running locally in dev env or deployed on vercel
const baseURL = process.env.NEXT_PUBLIC_VERCEL_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
: "http://localhost:3000";

// function to log user in with spotify
export default async function loginWithSpotify() {
const supabase = createClient();

Expand Down Expand Up @@ -40,11 +47,17 @@ export default async function loginWithSpotify() {
});
}

// redirect to spotify so the user can log in, then redirect back to /auth/callback
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "spotify",
options: {
// redirects to /auth/callback after going through the spotify login process
// this will ger/refresh the user data if they are already logged in,
// or redirect the user back to the home page to get their data if they are not logged in
redirectTo: process.env.NEXT_PUBLIC_REDIRECT_URI,
// this is the url the email verification that the user gets from supabase will redirect them to
emailRedirectTo: `${baseURL}/auth/confirm`,
// set the permissions the app needs to get the user data
scopes:
"user-read-private user-read-email user-library-read user-follow-read user-top-read user-modify-playback-state",
},
Expand Down
4 changes: 4 additions & 0 deletions src/app/page.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
Actual home page, just uses the home page from Index.jsx
*/

import HomePage from "./Index";

export default function IndexPage() {
Expand Down
Loading