Skip to content
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
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.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@tanstack/react-query": "^5.103.0",
Expand All @@ -20,6 +21,7 @@
"@rolldown/plugin-babel": "^0.2.3",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.6.7",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
Expand Down
72 changes: 72 additions & 0 deletions src/components/CharacterCardSmall.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { cleanup, render, screen } from "@testing-library/react";
import { describe, it, expect, vi, afterEach } from "vitest";
import CharacterCardSmall from "./CharacterCardSmall";
import "@testing-library/jest-dom/vitest";
import userEvent from "@testing-library/user-event";

afterEach(() => {
cleanup();
});

describe("CharacterCardSmall", () => {
it("displays the character name", () => {
render(
<CharacterCardSmall
name="Rick Sanchez"
image="rick.jpg"
isFavorite={false}
onToggleFavorite={vi.fn()}
onDetails={vi.fn()}
/>,
);

expect(screen.getByText("Rick Sanchez")).toBeInTheDocument();
});

it("displays the character image", () => {
render(
<CharacterCardSmall
name="Rick Sanchez"
image="rick.jpg"
isFavorite={false}
onToggleFavorite={vi.fn()}
onDetails={vi.fn()}
/>,
);

const image = screen.getByAltText("character image");
expect(image).toHaveAttribute("src", "rick.jpg");
});

it("calls onDetails when Details is clicked", async () => {
const user = userEvent.setup();
const onDetails = vi.fn();

render(
<CharacterCardSmall
name="Rick Sanchez"
image="rick.jpg"
isFavorite={false}
onToggleFavorite={vi.fn()}
onDetails={onDetails}
/>,
);

await user.click(screen.getByRole("button", { name: "Details" }));
expect(onDetails).toHaveBeenCalledOnce();
});

it("matches snapshot", () => {
const { container } = render(
<CharacterCardSmall
name="Rick Sanchez"
image="rick.jpg"
isFavorite={false}
onToggleFavorite={vi.fn()}
onDetails={vi.fn()}
/>,
);

expect(container).toMatchSnapshot();
});
});
212 changes: 212 additions & 0 deletions src/components/CharacterDetailCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import React from "react";
import { cleanup, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import userEvent from "@testing-library/user-event";

import CharacterDetailCard from "./CharacterDetailCard";
import type { Character } from "../types/Character";

const mockCharacter: Character = {
id: 1,
name: "Rick Sanchez",
species: "Human",
status: "Alive",
gender: "Male",
origin: {
name: "Earth (C-137)",
url: "https://rickandmortyapi.com/api/location/1",
},
image: "rick.jpg",
episode: [
"https://rickandmortyapi.com/api/episode/1",
"https://rickandmortyapi.com/api/episode/2",
],
};

function renderWithQueryClient(ui: React.ReactNode) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});

return render(
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
);
}

afterEach(() => {
cleanup();
});

describe("CharacterDetailCard", () => {
it("displays the character name", () => {
renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

expect(
screen.getByRole("heading", {
name: "Rick Sanchez",
}),
).toBeInTheDocument();
});

it("displays the character image", () => {
renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

const image = screen.getByAltText("Rick Sanchez");
expect(image).toHaveAttribute("src", "rick.jpg");
});

it("displays the character speice", () => {
renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

expect(screen.getByText("Human")).toBeInTheDocument();
});

it("displays the character status", () => {
renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

expect(screen.getByText(/Alive/)).toBeInTheDocument();
});

it("displays the character's origin", () => {
renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

expect(screen.getByText("Earth (C-137)")).toBeInTheDocument();
});

it("displays the character episodes", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => [
{
id: 1,
name: "Pilot",
episode: "S01E01",
},
{
id: 2,
name: "Lawnmower Dog",
episode: "S01E02",
},
],
}),
);

renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

expect(await screen.findByText("S01E01 - Pilot")).toBeInTheDocument();

expect(
await screen.findByText("S01E02 - Lawnmower Dog"),
).toBeInTheDocument();
});

it("calls onNext when next character button is clicked", async () => {
const user = userEvent.setup();
const onNext = vi.fn();

renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={onNext}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

await user.click(
screen.getByRole("button", {
name: "Next character",
}),
);

expect(onNext).toHaveBeenCalledOnce();
});

it("calls onPrevious when previous character button is clicked", async () => {
const user = userEvent.setup();
const onPrevious = vi.fn();

renderWithQueryClient(
<CharacterDetailCard
character={mockCharacter}
onClose={vi.fn()}
onPrevious={onPrevious}
onNext={vi.fn()}
isFavorite={false}
onToggleFavorite={vi.fn()}
/>,
);

await user.click(
screen.getByRole("button", {
name: "Previous character",
}),
);

expect(onPrevious).toHaveBeenCalledOnce();
});
});
39 changes: 39 additions & 0 deletions src/components/Searchbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom/vitest";
import Searchbar from "./Searchbar";
import { describe, it, expect, vi, afterEach } from "vitest";

afterEach(() => {
cleanup();
});

describe("Searchbar", () => {
it("Searches based on a searchQuery", () => {
const onSearchChange = vi.fn();
render(<Searchbar searchQuery="Rick" onSearchChange={onSearchChange} />);
const input = screen.getByRole("searchbox");

expect(input).toHaveValue("Rick");
});
it("Searches based on a searchQuery with capital letters", () => {
const onSearchChange = vi.fn();
render(<Searchbar searchQuery="rIcK" onSearchChange={onSearchChange} />);
const input = screen.getByRole("searchbox");

expect(input).toHaveValue("rIcK");
});

it("calls onSearchChange when user types", async () => {
const user = userEvent.setup();
const onSearchChange = vi.fn();

render(<Searchbar searchQuery="" onSearchChange={onSearchChange} />);

const input = screen.getByRole("searchbox");

await user.type(input, "Rick");

expect(onSearchChange).toHaveBeenCalled();
});
});
Loading