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
56 changes: 56 additions & 0 deletions src/components/ui/Avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { Avatar, AvatarStack } from "./Avatar";

// Mock next/image to verify rendered attributes straightforwardly
jest.mock("next/image", () => {
return function DummyImage(props: any) {
// eslint-disable-next-line @next/next/no-img-element
return <img alt={props.alt} src={props.src} width={props.width} height={props.height} className={props.className} data-unoptimized={props.unoptimized ? "true" : "false"} />;
};
});

describe("Avatar component (#278)", () => {
it("constructs dicebear URL with seed when src is not provided", () => {
render(<Avatar seed="alice" />);
const img = screen.getByRole("img", { name: "alice" });
expect(img).toHaveAttribute("src", "https://api.dicebear.com/9.x/identicon/svg?seed=alice&backgroundType=gradientLinear");
expect(img).toHaveAttribute("data-unoptimized", "true");
});

it("uses provided src when specified", () => {
render(<Avatar seed="alice" src="https://example.com/avatar.png" />);
const img = screen.getByRole("img", { name: "alice" });
expect(img).toHaveAttribute("src", "https://example.com/avatar.png");
expect(img).toHaveAttribute("data-unoptimized", "false");
});
});

describe("AvatarStack component (#278)", () => {
it("renders all avatars and no overflow badge when seeds count is less than max", () => {
const seeds = ["alice", "bob", "carol"];
render(<AvatarStack seeds={seeds} max={5} />);

expect(screen.getByRole("img", { name: "alice" })).toBeInTheDocument();
expect(screen.getByRole("img", { name: "bob" })).toBeInTheDocument();
expect(screen.getByRole("img", { name: "carol" })).toBeInTheDocument();
expect(screen.queryByText(/^\+/)).not.toBeInTheDocument();
});

it("renders all avatars and no overflow badge when seeds count equals max", () => {
const seeds = ["a", "b", "c", "d", "e"];
render(<AvatarStack seeds={seeds} max={5} />);

expect(screen.getAllByRole("img")).toHaveLength(5);
expect(screen.queryByText(/^\+/)).not.toBeInTheDocument();
});

it("renders max avatars and exact overflow badge '+N' when seeds count exceeds max", () => {
const seeds = ["a", "b", "c", "d", "e", "f", "g"];
render(<AvatarStack seeds={seeds} max={5} />);

expect(screen.getAllByRole("img")).toHaveLength(5);
const overflow = screen.getByText("+2");
expect(overflow).toBeInTheDocument();
});
});
50 changes: 50 additions & 0 deletions src/components/ui/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { Tabs } from "./Tabs";

describe("Tabs component (#277)", () => {
const tabs = [
{ key: "active", label: "Active", count: 3 },
{ key: "completed", label: "Completed", count: 0 },
{ key: "all", label: "All" },
];

it("renders all tab labels correctly", () => {
render(<Tabs tabs={tabs} active="active" onChange={jest.fn()} />);

expect(screen.getByRole("button", { name: /Active/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /Completed/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /All/i })).toBeInTheDocument();
});

it("calls onChange with the clicked tab key", () => {
const handleChange = jest.fn();
render(<Tabs tabs={tabs} active="active" onChange={handleChange} />);

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

expect(handleChange).toHaveBeenCalledTimes(1);
expect(handleChange).toHaveBeenCalledWith("completed");
});

it("applies active styles to the active tab and inactive styles to others", () => {
render(<Tabs tabs={tabs} active="active" onChange={jest.fn()} />);

const activeBtn = screen.getByRole("button", { name: /Active/i });
const completedBtn = screen.getByRole("button", { name: /Completed/i });

expect(activeBtn).toHaveClass("bg-white", "text-slate-900");
expect(completedBtn).toHaveClass("text-slate-500");
});

it("renders count badge only when tab.count is a number (including 0)", () => {
render(<Tabs tabs={tabs} active="active" onChange={jest.fn()} />);

expect(screen.getByText("3")).toBeInTheDocument();
expect(screen.getByText("0")).toBeInTheDocument();
// 'all' has no count, so only 2 count badges exist
const badges = screen.getAllByText(/^[0-9]+$/);
expect(badges).toHaveLength(2);
});
});