test(frontend): add ActivityFeed component tests + expand coverage slice

This commit is contained in:
Anya
2026-02-07 16:52:56 +00:00
parent 64581f4534
commit 15bc7ae833
5 changed files with 189 additions and 63 deletions

View File

@@ -0,0 +1,88 @@
import { render, screen } from "@testing-library/react";
import { ActivityFeed } from "./ActivityFeed";
type Item = { id: string; label: string };
describe("ActivityFeed", () => {
it("renders loading state when loading and no items", () => {
render(
<ActivityFeed<Item>
isLoading={true}
errorMessage={null}
items={[]}
renderItem={(item) => <div key={item.id}>{item.label}</div>}
/>,
);
expect(screen.getByText("Loading feed…")).toBeInTheDocument();
});
it("renders error state", () => {
render(
<ActivityFeed<Item>
isLoading={false}
errorMessage={"Boom"}
items={[]}
renderItem={(item) => <div key={item.id}>{item.label}</div>}
/>,
);
expect(screen.getByText("Boom")).toBeInTheDocument();
});
it("renders default error message when errorMessage is empty", () => {
render(
<ActivityFeed<Item>
isLoading={false}
errorMessage={""}
items={[]}
renderItem={(item) => <div key={item.id}>{item.label}</div>}
/>,
);
expect(screen.getByText("Unable to load feed.")).toBeInTheDocument();
});
it("renders empty state", () => {
render(
<ActivityFeed<Item>
isLoading={false}
errorMessage={null}
items={[]}
renderItem={(item) => <div key={item.id}>{item.label}</div>}
/>,
);
expect(
screen.getByText("Waiting for new comments…"),
).toBeInTheDocument();
expect(
screen.getByText("When agents post updates, they will show up here."),
).toBeInTheDocument();
});
it("renders items", () => {
const items: Item[] = [
{ id: "1", label: "First" },
{ id: "2", label: "Second" },
];
render(
<ActivityFeed<Item>
isLoading={false}
errorMessage={null}
items={items}
renderItem={(item) => (
<div key={item.id} data-testid="feed-item">
{item.label}
</div>
)}
/>,
);
expect(screen.getAllByTestId("feed-item")).toHaveLength(2);
expect(screen.getByText("First")).toBeInTheDocument();
expect(screen.getByText("Second")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,47 @@
import type { ReactNode } from "react";
type FeedItem = {
id: string;
};
type ActivityFeedProps<TItem extends FeedItem> = {
isLoading: boolean;
errorMessage?: string | null;
items: TItem[];
renderItem: (item: TItem) => ReactNode;
};
export function ActivityFeed<TItem extends FeedItem>({
isLoading,
errorMessage,
items,
renderItem,
}: ActivityFeedProps<TItem>) {
if (isLoading && items.length === 0) {
return <p className="text-sm text-slate-500">Loading feed</p>;
}
const hasError = errorMessage !== null && errorMessage !== undefined;
if (hasError) {
return (
<div className="rounded-lg border border-slate-200 bg-white p-4 text-sm text-slate-700 shadow-sm">
{errorMessage || "Unable to load feed."}
</div>
);
}
if (items.length === 0) {
return (
<div className="rounded-xl border border-slate-200 bg-white p-10 text-center shadow-sm">
<p className="text-sm font-medium text-slate-900">
Waiting for new comments
</p>
<p className="mt-1 text-sm text-slate-500">
When agents post updates, they will show up here.
</p>
</div>
);
}
return <div className="space-y-4">{items.map(renderItem)}</div>;
}