diff --git a/src/components/ui/Avatar.test.tsx b/src/components/ui/Avatar.test.tsx
new file mode 100644
index 0000000..a5cb824
--- /dev/null
+++ b/src/components/ui/Avatar.test.tsx
@@ -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
;
+ };
+});
+
+describe("Avatar component (#278)", () => {
+ it("constructs dicebear URL with seed when src is not provided", () => {
+ render();
+ 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();
+ 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();
+
+ 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();
+
+ 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();
+
+ expect(screen.getAllByRole("img")).toHaveLength(5);
+ const overflow = screen.getByText("+2");
+ expect(overflow).toBeInTheDocument();
+ });
+});
diff --git a/src/components/ui/Tabs.test.tsx b/src/components/ui/Tabs.test.tsx
new file mode 100644
index 0000000..06bcac8
--- /dev/null
+++ b/src/components/ui/Tabs.test.tsx
@@ -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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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);
+ });
+});