test(frontend): add ActivityFeed component tests + expand coverage slice
This commit is contained in:
88
frontend/src/components/activity/ActivityFeed.test.tsx
Normal file
88
frontend/src/components/activity/ActivityFeed.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
47
frontend/src/components/activity/ActivityFeed.tsx
Normal file
47
frontend/src/components/activity/ActivityFeed.tsx
Normal 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>;
|
||||
}
|
||||
Reference in New Issue
Block a user