From cfbb9b2d1a881d8f58f27528a873df74d6859b6e Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 6 Mar 2026 21:30:25 +0100 Subject: [PATCH] feat: move waiting_for_input badge from Inbox to HQ nav, remove Inbox entry Badge showing agents in waiting_for_input status now appears on HQ nav item. Inbox link removed from the nav. Adds regression test for navItems structure. --- apps/web/src/layouts/AppLayout.test.tsx | 66 +++++++++++++++++++++++++ apps/web/src/layouts/AppLayout.tsx | 3 +- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/layouts/AppLayout.test.tsx diff --git a/apps/web/src/layouts/AppLayout.test.tsx b/apps/web/src/layouts/AppLayout.test.tsx new file mode 100644 index 0000000..8d12ba8 --- /dev/null +++ b/apps/web/src/layouts/AppLayout.test.tsx @@ -0,0 +1,66 @@ +// @vitest-environment happy-dom +import { render, screen, within } from '@testing-library/react' +import '@testing-library/jest-dom/vitest' +import { vi } from 'vitest' +import { AppLayout } from './AppLayout' + +// Mock dependencies +vi.mock('@tanstack/react-router', () => ({ + Link: ({ children, to }: any) => { + const content = typeof children === 'function' ? children({ isActive: false }) : children + return {content} + }, +})) +vi.mock('@/components/ThemeToggle', () => ({ ThemeToggle: () => null })) +vi.mock('@/components/HealthDot', () => ({ HealthDot: () => null })) +vi.mock('@/components/NavBadge', () => ({ + NavBadge: ({ count }: { count: number }) => ( + count > 0 ? {count} : null + ), +})) + +const mockUseQuery = vi.hoisted(() => vi.fn()) +vi.mock('@/lib/trpc', () => ({ + trpc: { + listAgents: { useQuery: mockUseQuery }, + }, +})) + +beforeEach(() => { + vi.clearAllMocks() + mockUseQuery.mockReturnValue({ data: [] }) +}) + +describe('AppLayout navItems', () => { + it('renders HQ nav link', () => { + render({null}) + expect(screen.getByRole('link', { name: /hq/i })).toBeInTheDocument() + }) + + it('does not render Inbox nav link', () => { + render({null}) + expect(screen.queryByRole('link', { name: /inbox/i })).not.toBeInTheDocument() + }) + + it('shows badge on HQ when agents are waiting_for_input', () => { + mockUseQuery.mockReturnValue({ + data: [ + { id: '1', status: 'waiting_for_input' }, + { id: '2', status: 'running' }, + ], + }) + render({null}) + // NavBadge rendered next to HQ link (count=1) + const hqLink = screen.getByRole('link', { name: /hq/i }) + const badge = within(hqLink).getByTestId('nav-badge') + expect(badge).toHaveTextContent('1') + }) + + it('does not show questions badge on any Inbox link (Inbox removed)', () => { + mockUseQuery.mockReturnValue({ + data: [{ id: '1', status: 'waiting_for_input' }], + }) + render({null}) + expect(screen.queryByRole('link', { name: /inbox/i })).not.toBeInTheDocument() + }) +}) diff --git a/apps/web/src/layouts/AppLayout.tsx b/apps/web/src/layouts/AppLayout.tsx index 7dbc2eb..aa41888 100644 --- a/apps/web/src/layouts/AppLayout.tsx +++ b/apps/web/src/layouts/AppLayout.tsx @@ -7,11 +7,10 @@ import { trpc } from '@/lib/trpc' import type { ConnectionState } from '@/hooks/useConnectionStatus' const navItems = [ - { label: 'HQ', to: '/hq', badgeKey: null }, + { label: 'HQ', to: '/hq', badgeKey: 'questions' as const }, { label: 'Initiatives', to: '/initiatives', badgeKey: null }, { label: 'Agents', to: '/agents', badgeKey: 'running' as const }, { label: 'Radar', to: '/radar', badgeKey: null }, - { label: 'Inbox', to: '/inbox', badgeKey: 'questions' as const }, { label: 'Settings', to: '/settings', badgeKey: null }, ] as const