Skip to content

Commit

Permalink
add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
philipstubbs13 committed Sep 19, 2024
1 parent ce6a202 commit c874bd7
Show file tree
Hide file tree
Showing 29 changed files with 2,597 additions and 99 deletions.
21 changes: 21 additions & 0 deletions components/app-logo/AppLogo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { AppLogo } from "./AppLogo";

describe("AppLogo", () => {
test("should render", () => {
render(<AppLogo />);

expect(screen.getByTestId("app-logo")).toBeDefined();
});

describe("when backgroundColor is passed as prop", () => {
test("should apply style", () => {
render(<AppLogo backgroundColor="#fff" />);

expect(screen.getByTestId("app-logo")).toHaveStyle({
backgroundColor: "#fff",
});
});
});
});
1 change: 1 addition & 0 deletions components/app-logo/AppLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const AppLogo = (props: IProps) => {
"w-16 h-16 sm:w-20 sm:h-20": size === "large",
"w-8 h-8": size === "small",
})}
data-testid="app-logo"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
Expand Down
11 changes: 11 additions & 0 deletions components/avatar/Avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { Avatar } from "./Avatar";

describe("Avatar", () => {
test("should render", () => {
render(<Avatar />);

expect(screen.getByTestId("ui-avatar")).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const Avatar = (props: IProps) => {
"cursor-pointer": true,
[className]: className,
})}
data-testid="ui-avatar"
>
<AvatarImage alt={alt} src={props.image} />
<AvatarFallback>
Expand Down
69 changes: 69 additions & 0 deletions components/edit-photo-card/EditPhotoCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { render, screen, fireEvent, act } from "@testing-library/react";
import { EditPhotoCard } from "./EditPhotoCard";
import { IPhotoGalleryItem } from "@/components/photo-gallery/PhotoGallery.types";
import editRacePhoto from "@/app/actions/editRacePhoto";
import deleteRacePhoto from "@/app/actions/deleteRacePhoto";
import { vi } from "vitest";

vi.mock("@/app/actions/editRacePhoto");
vi.mock("@/app/actions/deleteRacePhoto");

describe("EditPhotoCard", () => {
const mockPhoto: IPhotoGalleryItem = {
id: "1",
caption: "Beautiful sunset",
image: "/test-image.jpg",
};

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

test("renders the photo card with image and caption", () => {
render(<EditPhotoCard photo={mockPhoto} />);

const image = screen.getByAltText(mockPhoto.caption);
const caption = screen.getByText(mockPhoto.caption);

expect(image).toBeInTheDocument();
expect(caption).toBeInTheDocument();
});

test("allows editing the caption", async () => {
render(<EditPhotoCard photo={mockPhoto} />);

const editButton = screen.getByRole("button", { name: /pencil/i });

act(() => {
fireEvent.click(editButton);
});

const input = screen.getByLabelText(/edit caption/i);

act(() => {
fireEvent.change(input, { target: { value: "New Caption" } });
});

expect(input).toHaveValue("New Caption");

const saveButton = screen.getByRole("button", { name: /save/i });

act(() => {
fireEvent.click(saveButton);
});

expect(editRacePhoto).toHaveBeenCalledWith(mockPhoto.id, "New Caption");
});

test("calls deleteRacePhoto when delete button is clicked", () => {
render(<EditPhotoCard photo={mockPhoto} />);

const deleteButton = screen.getByRole("button", { name: /trash/i });

act(() => {
fireEvent.click(deleteButton);
});

expect(deleteRacePhoto).toHaveBeenCalledWith(mockPhoto.id);
});
});
4 changes: 2 additions & 2 deletions components/edit-photo-card/EditPhotoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ export const EditPhotoCard = (props: IProps) => {
size="sm"
onClick={() => handleEdit(props.photo.id, props.photo.caption)}
>
<Pencil className="h-4 w-4" />
<Pencil className="h-4 w-4" aria-label="Pencil" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => handleDelete(props.photo.id)}
>
<Trash2 className="h-4 w-4" />
<Trash2 className="h-4 w-4" aria-label="Trash" />
</Button>
</div>
<p className="text-gray-700">{props.photo.caption}</p>
Expand Down
23 changes: 23 additions & 0 deletions components/footer/Footer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { Footer } from "./Footer";

describe("Footer", () => {
test("should render", () => {
render(<Footer />);

expect(screen.getAllByText(/runner pulse/i)).toHaveLength(2);
expect(screen.getAllByRole("link")).toHaveLength(9);
});

test("should display the current year in the footer", () => {
const currentYear = new Date().getFullYear();

render(<Footer />);

const footerText = screen.getByText(
${currentYear} Runner Pulse. All rights reserved.`
);
expect(footerText).toBeInTheDocument();
});
});
14 changes: 14 additions & 0 deletions components/footer/footer-quick-link/FooterQuickLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { FooterQuickLink } from "./FooterQuickLink";
import { Routes } from "@/utils/router/Routes.constants";

describe("FooterQuickLink", () => {
test("should render", () => {
render(<FooterQuickLink href={Routes.Results} label="Personal Results" />);

expect(
screen.getByRole("link", { name: "Personal Results" })
).toBeInTheDocument();
});
});
19 changes: 19 additions & 0 deletions components/footer/footer-social-link/FooterSocialLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { FooterSocialLink } from "./FooterSocialLink";
import { Instagram } from "lucide-react";

describe("FooterSocialLink", () => {
test("should render", () => {
render(
<FooterSocialLink href={"www.instagram.com"} label="Instagram">
<Instagram />
</FooterSocialLink>
);

const link = screen.getByRole("link", { name: "Instagram" });

expect(link).toBeInTheDocument();
expect(link).toHaveAttribute("target", "_blank");
});
});
11 changes: 11 additions & 0 deletions components/gradient-container/GradientContainer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { GradientContainer } from "./GradientContainer";

describe("GradientContainer", () => {
test("should render children", () => {
render(<GradientContainer>child</GradientContainer>);

expect(screen.getByText(/child/i)).toBeInTheDocument();
});
});
102 changes: 102 additions & 0 deletions components/header/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { Header } from "./Header";
import { signOut, useSession } from "next-auth/react";
import { useGlobalContext } from "@/context/global-context/GlobalContext";
import { useRouter } from "next/navigation";
import { Mock, vi } from "vitest";
import { Tab } from "../tabs/Tabs.constants";
import { Routes } from "@/utils/router/Routes.constants";

vi.mock("next-auth/react");
vi.mock("@/context/global-context/GlobalContext");
vi.mock("next/navigation");

describe("Header", () => {
const mockRouter = { push: vi.fn() };
const mockUpdateActiveTab = vi.fn();

const mockSession = {
data: {
user: {
name: "John Doe",
email: "john@example.com",
image: "/john-doe.jpg",
},
},
};

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

(useRouter as Mock).mockReturnValue(mockRouter);
(useSession as Mock).mockReturnValue(mockSession);
(useGlobalContext as Mock).mockReturnValue({
activeTab: Tab.Gallery,
updateActiveTab: mockUpdateActiveTab,
});
});

test("should render the user's profile information", () => {
render(<Header />);

const avatarFallback = screen.getByText("JD");

expect(avatarFallback).toBeInTheDocument();
});

test("should open the profile sheet when avatar is clicked", () => {
render(<Header />);

const avatar = screen.getByText("JD");
fireEvent.click(avatar);

const profileTitle = screen.getByText("Profile");
const userName = screen.getByText("John Doe");
const userEmail = screen.getByText("john@example.com");

expect(userName).toBeInTheDocument();
expect(userEmail).toBeInTheDocument();
expect(profileTitle).toBeInTheDocument();
});

test("should navigate to manage photos when the manage photos button is clicked", () => {
render(<Header />);

const avatar = screen.getByText("JD");
fireEvent.click(avatar);

const managePhotosButton = screen.getByRole("button", {
name: /manage photos/i,
});
fireEvent.click(managePhotosButton);

expect(mockRouter.push).toHaveBeenCalledWith(Routes.ManagePhotos);
});

test("should navigate to manage race distances when the manage race distances button is clicked", () => {
render(<Header />);

const avatar = screen.getByText("JD");
fireEvent.click(avatar);

const manageRaceDistancesButton = screen.getByRole("button", {
name: /manage race distances/i,
});
fireEvent.click(manageRaceDistancesButton);

expect(mockRouter.push).toHaveBeenCalledWith(Routes.ManageRaceDistances);
});

test("should call signOut and updates the active tab when the logout button is clicked", () => {
render(<Header />);

const avatar = screen.getByText("JD");
fireEvent.click(avatar);

const logoutButton = screen.getByRole("button", { name: /logout/i });
fireEvent.click(logoutButton);

expect(mockUpdateActiveTab).toHaveBeenCalledWith(Tab.Results);
expect(signOut).toHaveBeenCalled();
});
});
51 changes: 51 additions & 0 deletions components/no-results/NoResults.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render, screen } from "@testing-library/react";
import { NoResults } from "./NoResults";
import { Tab } from "@/components/tabs/Tabs.constants";
import { ComponentProps, ComponentType } from "react";

const MockIcon: ComponentType<unknown> = () => <svg data-testid="mock-icon" />;

describe("NoResults", () => {
const defaultProps: ComponentProps<typeof NoResults> = {
description: "No results found.",
Icon: MockIcon,
tab: Tab.Results,
title: "No Results",
};

test("should renders the title, description, and icon correctly", () => {
render(<NoResults {...defaultProps} />);

const title = screen.getByText("No Results");
const description = screen.getByText("No results found.");
const icon = screen.getByTestId("mock-icon");

expect(title).toBeInTheDocument();
expect(description).toBeInTheDocument();
expect(icon).toBeInTheDocument();
});

test("should apply correct styles when Tab is Results", () => {
render(<NoResults {...defaultProps} tab={Tab.Results} />);

const container = screen.getByText("No Results").closest("div");

expect(container).toHaveClass("bg-blue-50");
});

test("should apply correct styles when Tab is Gallery", () => {
render(<NoResults {...defaultProps} tab={Tab.Gallery} />);

const container = screen.getByText("No Results").closest("div");

expect(container).toHaveClass("bg-pink-50");
});

test("should apply correct styles when Tab is Settings", () => {
render(<NoResults {...defaultProps} tab={Tab.Settings} />);

const container = screen.getByText("No Results").closest("div");

expect(container).toHaveClass("bg-blue-50");
});
});
Loading

0 comments on commit c874bd7

Please sign in to comment.