Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NavLink } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import { routes } from '../routes'
import { useAuth } from './AuthContext'
import ThemeToggle from './ThemeToggle'
import { useTheme } from './ThemeContext'
import { useSidebar } from '../context/SidebarContext'

interface NavItemProps {
Expand All @@ -22,7 +22,7 @@ const NavItem = ({ to, icon, label, isExpanded, highlight = false }: NavItemProp
onClick={(e) => e.stopPropagation()}
className={({ isActive }) => `
relative flex items-center transition-all duration-300 group
${isExpanded ? 'gap-3 px-5 py-2.5 mx-2 rounded-lg' : 'justify-center py-3 px-2 mx-2 rounded-lg'}
${isExpanded ? 'gap-3 px-5 py-3 mx-2 rounded-lg' : 'justify-center py-3 px-2 mx-2 rounded-lg'}
${isActive
? 'bg-accent-silver/10 text-primary shadow-[inset_0_1px_1px_rgba(255,255,255,0.05)]'
: highlight
Expand Down Expand Up @@ -103,6 +103,8 @@ const NavSection = ({ label, isExpanded }: { label: string, isExpanded: boolean
)

export default function Sidebar() {
const { theme, toggleTheme } = useTheme()

const { isExpanded, toggleSidebar } = useSidebar()
const { isAuthenticated, signOut } = useAuth()

Expand Down Expand Up @@ -157,10 +159,8 @@ export default function Sidebar() {

<NavSection label="Analyze" isExpanded={isExpanded} />
<NavItem to={routes.findings} icon="emergency_home" label="Findings" isExpanded={isExpanded} />

<NavItem to={routes.reports} icon="summarize" label="Reports" isExpanded={isExpanded} />
<NavItem to={routes.workflows} icon="account_tree" label="Workflows" isExpanded={isExpanded} />

</div>

{/* Bottom Actions */}
Expand All @@ -183,19 +183,29 @@ export default function Sidebar() {
)}
</button>
)}
<div className="flex items-center gap-2">
<ThemeToggle size="sm" />

<div className={`flex items-center mt-4 gap-2 ${isExpanded ? 'flex-row' : 'flex-col'}`}>
<button
onClick={() => toggleSidebar()}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
toggleSidebar()
}
onClick={(e) => {
e.stopPropagation();
toggleTheme();
}}
title="Toggle theme"
className="flex-1 w-full py-2 flex items-center justify-center rounded-lg text-muted hover:text-primary hover:bg-accent-silver/5 transition-colors"
>
<span className="material-symbols-outlined text-[18px]">
{theme === 'dark' ? 'dark_mode' : 'light_mode'}
</span>
</button>

<button
onClick={(e) => {
e.stopPropagation();
toggleSidebar();
}}
aria-label={isExpanded ? 'Collapse sidebar' : 'Expand sidebar'}
aria-controls="sidebar-nav"
className="flex-1 py-2 flex items-center justify-center text-muted hover:text-primary transition-colors rounded hover:bg-accent-silver/5 focus:outline-none focus:ring-2 focus:ring-rag-red/50"
title={isExpanded ? "Collapse sidebar" : "Expand sidebar"}
aria-label={isExpanded ? "Collapse sidebar" : "Expand sidebar"}
className="flex-1 w-full py-2 flex items-center justify-center rounded-lg text-muted hover:text-primary hover:bg-accent-silver/5 transition-colors focus:ring-2 focus:ring-rag-red/50"
>
<span className="material-symbols-outlined text-[18px]">
{isExpanded ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'}
Expand Down
58 changes: 57 additions & 1 deletion frontend/testing/unit/components/Sidebar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BrowserRouter } from 'react-router-dom'
Expand Down Expand Up @@ -151,3 +151,59 @@ describe('Sidebar - Accessibility', () => {
}, 100)
})
})
// --- Regression coverage for #2362 review: restored nav + auth actions ---

const authState = vi.hoisted(() => ({
isAuthenticated: true,
signOut: vi.fn(),
}))

vi.mock('../../../src/components/AuthContext', () => ({
useAuth: () => authState,
}))

describe('Sidebar - Navigation & Auth Actions (regression)', () => {
beforeEach(() => {
localStorage.clear()
authState.isAuthenticated = true
authState.signOut = vi.fn()
})

it('should retain the Workflows nav item after layout changes', () => {
renderSidebar()
expect(screen.getByRole('link', { name: /workflows/i })).toBeInTheDocument()
})

it('should retain the Reports and Settings nav items after layout changes', () => {
renderSidebar()
expect(screen.getByRole('link', { name: /reports/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /settings/i })).toBeInTheDocument()
})

it('should show the Sign Out action when the user is authenticated', () => {
authState.isAuthenticated = true
renderSidebar()
expect(screen.getByLabelText(/sign out/i)).toBeInTheDocument()
})

it('should not show the Sign Out action when the user is not authenticated', () => {
authState.isAuthenticated = false
renderSidebar()
expect(screen.queryByLabelText(/sign out/i)).not.toBeInTheDocument()
})

it('should call signOut when the Sign Out action is clicked', () => {
authState.isAuthenticated = true
renderSidebar()
const signOutButton = screen.getByLabelText(/sign out/i)

fireEvent.click(signOutButton)

expect(authState.signOut).toHaveBeenCalledTimes(1)
})

it('should still show the theme toggle alongside the retained auth/nav actions', () => {
renderSidebar()
expect(screen.getByTitle(/toggle theme/i)).toBeInTheDocument()
})
})
Loading