diff --git a/app/src/components/layout/shell/CollapsedNavRail.test.tsx b/app/src/components/layout/shell/CollapsedNavRail.test.tsx index 93ac0c8ed..dd33aac00 100644 --- a/app/src/components/layout/shell/CollapsedNavRail.test.tsx +++ b/app/src/components/layout/shell/CollapsedNavRail.test.tsx @@ -19,10 +19,11 @@ vi.mock('../../../services/analytics', () => ({ trackEvent: vi.fn() })); describe('CollapsedNavRail', () => { beforeEach(() => vi.clearAllMocks()); - it('renders Home plus every primary nav destination as an icon button', () => { + it('renders Home, Wallet, and every primary nav destination as icon buttons', () => { renderWithProviders(, { initialEntries: ['/home'] }); for (const key of [ 'nav.home', + 'nav.wallet', 'nav.chat', 'nav.human', 'nav.brain', @@ -33,6 +34,26 @@ describe('CollapsedNavRail', () => { } }); + it('wallet button navigates to /settings/wallet-balances', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + fireEvent.click(screen.getByRole('button', { name: 'nav.wallet' })); + expect(mockNavigate).toHaveBeenCalledWith('/settings/wallet-balances'); + }); + + it('wallet button has correct data-analytics-id', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + expect(screen.getByRole('button', { name: 'nav.wallet' })).toHaveAttribute( + 'data-analytics-id', + 'collapsed-rail-wallet' + ); + }); + + it('wallet button is marked active when on /settings/wallet-balances', () => { + renderWithProviders(, { initialEntries: ['/settings/wallet-balances'] }); + const btn = screen.getByRole('button', { name: 'nav.wallet' }); + expect(btn.className).toMatch(/bg-white|dark:bg-neutral-800/); + }); + it('navigates to a destination path when its icon is clicked', () => { renderWithProviders(, { initialEntries: ['/home'] }); fireEvent.click(screen.getByRole('button', { name: 'nav.brain' })); diff --git a/app/src/components/layout/shell/CollapsedNavRail.tsx b/app/src/components/layout/shell/CollapsedNavRail.tsx index 4a5ce1a87..a828f4f04 100644 --- a/app/src/components/layout/shell/CollapsedNavRail.tsx +++ b/app/src/components/layout/shell/CollapsedNavRail.tsx @@ -67,6 +67,24 @@ export default function CollapsedNavRail() { + {/* Wallet shortcut — mirrors SidebarHeader wallet button for collapsed state. */} + + {/* Primary nav destinations */} {tabs.map(tab => { const active = matchActive(tab.path, location.pathname); diff --git a/app/src/components/layout/shell/SidebarHeader.test.tsx b/app/src/components/layout/shell/SidebarHeader.test.tsx new file mode 100644 index 000000000..e8ba41253 --- /dev/null +++ b/app/src/components/layout/shell/SidebarHeader.test.tsx @@ -0,0 +1,69 @@ +import { fireEvent, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { renderWithProviders } from '../../../test/test-utils'; +import SidebarHeader from './SidebarHeader'; + +const mockNavigate = vi.fn(); +const mockHome = vi.fn(); +const mockHide = vi.fn(); + +vi.mock('react-router-dom', async importOriginal => { + const actual = await importOriginal(); + return { ...actual, useNavigate: () => mockNavigate }; +}); +vi.mock('./useHomeNav', () => ({ useHomeNav: () => mockHome })); +vi.mock('./RootShellLayout', () => ({ useRootSidebar: () => ({ hide: mockHide }) })); +// Return i18n keys verbatim so queries don't depend on locale. +vi.mock('../../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (k: string) => k }) })); + +describe('SidebarHeader', () => { + beforeEach(() => vi.clearAllMocks()); + + it('renders Home, Wallet, Settings, and Collapse buttons', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + expect(screen.getByRole('button', { name: 'nav.home' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'nav.wallet' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'nav.settings' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'chat.hideSidebar' })).toBeInTheDocument(); + }); + + it('wallet button navigates to /settings/wallet-balances', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + fireEvent.click(screen.getByRole('button', { name: 'nav.wallet' })); + expect(mockNavigate).toHaveBeenCalledWith('/settings/wallet-balances'); + }); + + it('wallet button has correct data-analytics-id', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + expect(screen.getByRole('button', { name: 'nav.wallet' })).toHaveAttribute( + 'data-analytics-id', + 'sidebar-header-wallet' + ); + }); + + it('wallet button has matching aria-label and title', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + const btn = screen.getByRole('button', { name: 'nav.wallet' }); + expect(btn).toHaveAttribute('aria-label', 'nav.wallet'); + expect(btn).toHaveAttribute('title', 'nav.wallet'); + }); + + it('settings button navigates to /settings', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + fireEvent.click(screen.getByRole('button', { name: 'nav.settings' })); + expect(mockNavigate).toHaveBeenCalledWith('/settings'); + }); + + it('Home button invokes the shared Home action', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + fireEvent.click(screen.getByRole('button', { name: 'nav.home' })); + expect(mockHome).toHaveBeenCalledTimes(1); + }); + + it('Collapse button calls hide()', () => { + renderWithProviders(, { initialEntries: ['/home'] }); + fireEvent.click(screen.getByRole('button', { name: 'chat.hideSidebar' })); + expect(mockHide).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/src/components/layout/shell/SidebarHeader.tsx b/app/src/components/layout/shell/SidebarHeader.tsx index c5901588b..9b7ab3075 100644 --- a/app/src/components/layout/shell/SidebarHeader.tsx +++ b/app/src/components/layout/shell/SidebarHeader.tsx @@ -37,6 +37,24 @@ export default function SidebarHeader() {
+ {/* Wallet shortcut — one-click access to wallet balances. */} + +