Skip to content

Commit

Permalink
Merge pull request #43 from Firebird1029/comments_and_cleanup
Browse files Browse the repository at this point in the history
Added comments and increased test coverage
  • Loading branch information
Firebird1029 authored Apr 13, 2024
2 parents 48b581b + 61fd563 commit 07016df
Show file tree
Hide file tree
Showing 25 changed files with 346 additions and 137 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 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();
});
});
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

0 comments on commit 07016df

Please sign in to comment.