From 8ecfe1ddb826e01b8f5867e014239e2e2bb7258f Mon Sep 17 00:00:00 2001 From: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Date: Mon, 13 Apr 2026 08:16:38 +0200 Subject: [PATCH] fix(ui): align activity heading count with sidebar --- src/App.test.tsx | 69 +++++++++++++++++-- src/App.tsx | 3 +- .../ActivityFeed/ActivityFeed.test.tsx | 13 ++++ src/components/ActivityFeed/ActivityFeed.tsx | 5 +- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 464313d..df564a1 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -2,7 +2,10 @@ import { describe, expect, it, vi, beforeEach } from "vitest"; import { act, render, screen } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; +import { useGitHubData } from "./hooks/useGitHubData"; import { useDashboardStore } from "./stores/dashboard"; +import type { Activity } from "./lib/types/github"; +import type { DashboardData, DashboardStats } from "./lib/types/dashboard"; vi.mock("./lib/open", () => ({ openUrl: vi.fn(), @@ -18,16 +21,55 @@ vi.mock("./lib/tauri", async (importOriginal) => { }); vi.mock("./hooks/useGitHubData", () => ({ - useGitHubData: vi.fn().mockReturnValue({ - dashboard: { syncedAt: "2026-03-28T10:00:00Z", reviewRequests: [], myPullRequests: [], assignedIssues: [], recentActivity: [], workspaces: [] }, - stats: { pendingReviews: 3, openPrs: 5, openIssues: 2, totalWorkspaces: 1, unreadActivity: 0 }, + useGitHubData: vi.fn(), +})); + +function makeActivity(id: string): Activity { + return { + id, + activityType: "comment_added", + actor: "alice", + repoId: "repo-1", + pullRequestId: "pr-1", + issueId: null, + message: "Some comment", + isRead: false, + createdAt: "2026-03-28T10:00:00Z", + }; +} + +function makeGitHubDataMock(overrides?: { + readonly dashboard?: Partial; + readonly stats?: Partial; +}): ReturnType { + return { + dashboard: { + syncedAt: "2026-03-28T10:00:00Z", + reviewRequests: [], + myPullRequests: [], + assignedIssues: [], + recentActivity: [], + workspaces: [], + ...overrides?.dashboard, + }, + stats: { + pendingReviews: 3, + openPrs: 5, + openIssues: 2, + totalWorkspaces: 1, + unreadActivity: 0, + ...overrides?.stats, + }, isLoading: false, error: null, authExpired: false, + syncError: null, forceSync: vi.fn(), isSyncing: false, - }), -})); + }; +} + +const mockedUseGitHubData = vi.mocked(useGitHubData); function renderApp() { const queryClient = new QueryClient({ @@ -42,6 +84,7 @@ function renderApp() { describe("App layout", () => { beforeEach(() => { + mockedUseGitHubData.mockReturnValue(makeGitHubDataMock()); useDashboardStore.setState({ currentView: "overview", activeFilters: {}, @@ -104,6 +147,21 @@ describe("App layout", () => { expect(await screen.findByTestId("activity-feed")).toBeInTheDocument(); }); + it("should use unread activity stats for the activity heading", async () => { + mockedUseGitHubData.mockReturnValue( + makeGitHubDataMock({ + dashboard: { recentActivity: [makeActivity("act-1")] }, + stats: { unreadActivity: 149 }, + }), + ); + useDashboardStore.setState({ currentView: "feed" }); + + renderApp(); + + expect(await screen.findByTestId("activity-feed")).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Activity 149" })).toBeInTheDocument(); + }); + it("should render settings view", async () => { useDashboardStore.setState({ currentView: "settings" }); renderApp(); @@ -153,6 +211,7 @@ describe("App keyboard shortcuts", () => { let mockedOpenUrl: ReturnType; beforeEach(async () => { + mockedUseGitHubData.mockReturnValue(makeGitHubDataMock()); // Use "feed" view — it has no useRegisterNavigableItems hook, // so pre-seeded navigableItems are preserved for keyboard tests. useDashboardStore.setState({ diff --git a/src/App.tsx b/src/App.tsx index 48335b6..9d40761 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -107,7 +107,7 @@ interface MainContentProps { } function MainContent({ view, onBackToDashboard }: MainContentProps): ReactElement { - const { dashboard } = useGitHubData(); + const { dashboard, stats } = useGitHubData(); const queryClient = useQueryClient(); const markAllRead = useMutation({ @@ -184,6 +184,7 @@ function MainContent({ view, onBackToDashboard }: MainContentProps): ReactElemen return ( markAllRead.mutate()} /> ); diff --git a/src/components/ActivityFeed/ActivityFeed.test.tsx b/src/components/ActivityFeed/ActivityFeed.test.tsx index ee1a6ee..47a6d7a 100644 --- a/src/components/ActivityFeed/ActivityFeed.test.tsx +++ b/src/components/ActivityFeed/ActivityFeed.test.tsx @@ -200,6 +200,19 @@ describe("ActivityFeed", () => { expect(screen.getByText("6")).toBeInTheDocument(); }); + it("should prefer an explicit header count over the visible activity count", () => { + render( + , + ); + + expect(screen.getByRole("heading", { name: "Activity 149" })).toBeInTheDocument(); + expect(screen.queryByRole("heading", { name: "Activity 1" })).not.toBeInTheDocument(); + }); + it("should hide section header when hideHeader is true", () => { render(); diff --git a/src/components/ActivityFeed/ActivityFeed.tsx b/src/components/ActivityFeed/ActivityFeed.tsx index 891822f..a639d54 100644 --- a/src/components/ActivityFeed/ActivityFeed.tsx +++ b/src/components/ActivityFeed/ActivityFeed.tsx @@ -12,6 +12,7 @@ import { ActivityItem } from "./ActivityItem"; interface ActivityFeedProps { readonly activities: readonly Activity[]; + readonly headerCount?: number; readonly isLoading?: boolean; readonly onMarkAllRead: () => void; readonly hideHeader?: boolean; @@ -56,6 +57,7 @@ function matchesFilter(activity: Activity, filter: FilterType): boolean { export function ActivityFeed({ activities, + headerCount, isLoading = false, onMarkAllRead, hideHeader = false, @@ -79,6 +81,7 @@ export function ActivityFeed({ value.toLowerCase().includes(normalizedQuery), ); }); + const sectionCount = isLoading ? undefined : headerCount ?? visible.length; return (
- {!hideHeader && } + {!hideHeader && } {isLoading ? ( <>