Skip to content
Open
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
44 changes: 44 additions & 0 deletions react-ystemandchess/src/components/footer/Footer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { render, screen } from "@testing-library/react";
import Footer from "./Footer";

test("renders social media links with correct hrefs", () => {
render(<Footer />);

const twitter = screen.getByAltText("twitter-icon");
const instagram = screen.getByAltText("instagram-icon");
const facebook = screen.getByAltText("facebook-icon");
const google = screen.getByAltText("google-icon");

expect(twitter).toBeInTheDocument();
expect(instagram).toBeInTheDocument();
expect(facebook).toBeInTheDocument();
expect(google).toBeInTheDocument();

expect(twitter.closest("a")?.getAttribute("href")).toBe("/");
expect(instagram.closest("a")?.getAttribute("href")).toBe(
"https://www.instagram.com/stemwithstemy/",
);
expect(facebook.closest("a")?.getAttribute("href")).toBe(
"https://www.facebook.com/YSTEMandChess/",
);
expect(google.closest("a")?.getAttribute("href")).toBe("/");
});

test("renders sponsor logos", () => {
render(<Footer />);

expect(screen.getByAltText("ventive-logo")).toBeInTheDocument();
expect(screen.getByAltText("kount-logo")).toBeInTheDocument();
expect(screen.getByAltText("idahoCentral-logo")).toBeInTheDocument();
expect(screen.getByAltText("PH-logo")).toBeInTheDocument();
});

test("renders partner logos", () => {
render(<Footer />);

expect(screen.getByAltText("boiseRescue-logo")).toBeInTheDocument();
expect(screen.getByAltText("boysAndGirls-logo")).toBeInTheDocument();
expect(screen.getByAltText("possible-logo")).toBeInTheDocument();
expect(screen.getByAltText("boiseDistrict-logo")).toBeInTheDocument();
expect(screen.getByAltText("rotary-logo")).toBeInTheDocument();
});
8 changes: 4 additions & 4 deletions react-ystemandchess/src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* Footer Component
*
*
* This component displays the website footer with contact information,
* social media links, sponsors, and partners.
*
*
* Features:
* - Contact information (email and phone)
* - Social media icon links
Expand Down Expand Up @@ -42,14 +42,14 @@ const Footer = () => {
<a href="/">
<img src={TwitterIcon} alt="twitter-icon" id="twitter-icon" />
</a>
<a href="/">
<a href="https://www.instagram.com/stemwithstemy/">
<img
src={InstagramIcon}
alt="instagram-icon"
id="instagram-icon"
/>
</a>
<a href="/">
<a href="https://www.facebook.com/YSTEMandChess/">
<img src={FacebookIcon} alt="facebook-icon" id="facebook-icon" />
</a>
<a href="/">
Expand Down
185 changes: 185 additions & 0 deletions react-ystemandchess/src/components/navbar/NavBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router";
import NavBar from "./NavBar";

jest.mock("../../globals", () => ({
SetPermissionLevel: jest.fn(),
}));

jest.mock("react-cookie", () => ({
useCookies: jest.fn(),
}));

jest.mock("framer-motion", () => {
const React = require("react");
return {
motion: {
div: ({ children, ...rest }: any) => <div {...rest}>{children}</div>,
},
};
});

jest.mock("@fortawesome/react-fontawesome", () => ({
FontAwesomeIcon: (props: any) => <span data-testid="fa-icon" {...props} />,
}));

jest.mock(
"react-router-dom",
() => {
const React = require("react");
return {
Link: ({ to, children, ...rest }: any) => (
<a href={typeof to === "string" ? to : "#"} {...rest}>
{children}
</a>
),
};
},
{ virtual: true },
);

import { SetPermissionLevel } from "../../globals";
import { useCookies } from "react-cookie";

describe("NavBar", () => {
const mockedSetPermissionLevel = SetPermissionLevel as jest.Mock;
const mockedUseCookies = useCookies as jest.Mock;

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

const renderNavBar = () =>
render(
<MemoryRouter>
<NavBar />
</MemoryRouter>,
);

test("renders Login link when user is logged out", async () => {
mockedUseCookies.mockReturnValue([{}, jest.fn(), jest.fn()]);
mockedSetPermissionLevel.mockResolvedValue({ error: "unauthenticated" });

renderNavBar();

expect(await screen.findByText(/Login/i)).toBeInTheDocument();
expect(screen.queryByText(/Alice/i)).toBeNull();
});

test("renders username and hides Login when user is logged in", async () => {
mockedUseCookies.mockReturnValue([
{ login: "mockToken" },
jest.fn(),
jest.fn(),
]);
mockedSetPermissionLevel.mockResolvedValue({
username: "Alice",
role: "student",
});

renderNavBar();

const usernameButton = await screen.findByRole("button", {
name: /Alice/i,
});
expect(screen.queryByText(/Login/i)).toBeNull();

fireEvent.click(usernameButton);
const profileLink = await screen.findByText(/Profile/i);
expect(profileLink.closest("a")).toHaveAttribute(
"href",
"/student-profile",
);
});

test("shows Add Student in profile dropdown for parent role", async () => {
mockedUseCookies.mockReturnValue([
{ login: "mockToken" },
jest.fn(),
jest.fn(),
]);
mockedSetPermissionLevel.mockResolvedValue({
username: "Bob",
role: "parent",
});

renderNavBar();

const usernameButton = await screen.findByRole("button", {
name: /Bob/i,
});
fireEvent.click(usernameButton);

const addStudentLink = await screen.findByText(/Add Student/i);
expect(addStudentLink.closest("a")).toHaveAttribute(
"href",
"/parent-add-student",
);
});

test("toggles About Us dropdown open and closes on outside click", async () => {
mockedUseCookies.mockReturnValue([{}, jest.fn(), jest.fn()]);
mockedSetPermissionLevel.mockResolvedValue({ error: "unauthenticated" });

renderNavBar();

const aboutUsTrigger = screen.getByText(/About Us/i);
fireEvent.click(aboutUsTrigger);

expect(
await screen.findByText(/Benefit of Computer Science/i),
).toBeInTheDocument();

fireEvent.mouseDown(document.body);

await waitFor(() =>
expect(screen.queryByText(/Benefit of Computer Science/i)).toBeNull(),
);
});

test("mobile menu toggles navigation visibility", async () => {
mockedUseCookies.mockReturnValue([{}, jest.fn(), jest.fn()]);
mockedSetPermissionLevel.mockResolvedValue({ error: "unauthenticated" });

renderNavBar();

const navsBefore = screen.getAllByRole("navigation").length;
const toggleBtn = screen.getByRole("button", { name: /toggle menu/i });

fireEvent.click(toggleBtn);
const navsOpen = screen.getAllByRole("navigation").length;
expect(navsOpen).toBeGreaterThan(navsBefore);

fireEvent.click(toggleBtn);
const navsClosed = screen.getAllByRole("navigation").length;
expect(navsClosed).toBe(navsBefore);
});

test("logout removes cookies and redirects to /login", async () => {
const removeCookieMock = jest.fn();
mockedUseCookies.mockReturnValue([
{ login: "mockToken" },
jest.fn(),
removeCookieMock,
]);
mockedSetPermissionLevel.mockResolvedValue({
username: "Alice",
role: "student",
});

renderNavBar();

const usernameButton = await screen.findByRole("button", {
name: /Alice/i,
});
fireEvent.click(usernameButton);

const logoutBtn = await screen.findByText(/Log Out/i);
fireEvent.click(logoutBtn);

expect(removeCookieMock).toHaveBeenCalledWith("login");
expect(removeCookieMock).toHaveBeenCalledWith("eventId");
expect(removeCookieMock).toHaveBeenCalledWith("timerStatus");
});
});
Original file line number Diff line number Diff line change
@@ -1,28 +1,49 @@
import { render } from "@testing-library/react";
import { render, screen, act } from "@testing-library/react";
import { useChessSocket } from "./useChessSocket";

// Mock socket.io-client for Jest
jest.mock("socket.io-client", () => ({
io: () => ({
on: jest.fn(),
emit: jest.fn(),
disconnect: jest.fn(),
id: "test-socket-id",
}),
}));

// Dummy component to execute the hook
const HookExecutor = () => {
useChessSocket({
jest.mock("socket.io-client");

const SocketStatus = () => {
const socket = useChessSocket({
student: "test-student",
serverUrl: "http://localhost",
onMove: () => {},
serverUrl: "http://localhost", // <- required field
});
return null;
return <div data-testid="socket-connected">{String(socket.connected)}</div>;
};

describe("useChessSocket Hook (CI Stub)", () => {
it("initializes without crashing", () => {
render(<HookExecutor />);
describe("useChessSocket connection/disconnection", () => {
it("updates connected state on connect/disconnect and disconnects on unmount", () => {
const { io } = require("socket.io-client");
const mockSocket = {
on: jest.fn(),
emit: jest.fn(),
disconnect: jest.fn(),
};
io.mockReturnValue(mockSocket);

const { unmount } = render(<SocketStatus />);

const connectHandler = mockSocket.on.mock.calls.find(
(c: any[]) => c[0] === "connect",
)?.[1];
const disconnectHandler = mockSocket.on.mock.calls.find(
(c: any[]) => c[0] === "disconnect",
)?.[1];

expect(screen.getByTestId("socket-connected")).toHaveTextContent("false");

act(() => {
connectHandler && connectHandler();
});
expect(screen.getByTestId("socket-connected")).toHaveTextContent("true");

act(() => {
disconnectHandler && disconnectHandler("io client disconnect");
});
expect(screen.getByTestId("socket-connected")).toHaveTextContent("false");

unmount();
expect(mockSocket.disconnect).toHaveBeenCalled();
});
});