import { render, screen, fireEvent } from '@testing-library/react'
import { Sidebar, SidebarLayout } from '../sidebar'
// next/link and next/navigation are mocked via jest.config.js moduleNameMapper
const sections = [
{
title: 'Main',
links: [
{ href: '/', label: 'Dashboard', icon: },
{ href: '/contacts', label: 'Contacts' },
],
},
{
title: 'Admin',
links: [
{ href: '/settings', label: 'Account Settings' },
],
},
]
describe('Sidebar', () => {
it('renders app name', () => {
render()
expect(screen.getByText('MarketSimpli')).toBeInTheDocument()
})
it('renders section titles', () => {
render()
expect(screen.getByText('Main')).toBeInTheDocument()
expect(screen.getByText('Admin')).toBeInTheDocument()
})
it('renders all nav links', () => {
render()
expect(screen.getByText('Dashboard')).toBeInTheDocument()
expect(screen.getByText('Contacts')).toBeInTheDocument()
expect(screen.getByText('Account Settings')).toBeInTheDocument()
})
it('renders icons for links that have icons', () => {
render()
expect(screen.getByTestId('icon-dashboard')).toBeInTheDocument()
})
it('renders collapse toggle button when collapsible is true (default)', () => {
render()
expect(screen.getByText('Collapse')).toBeInTheDocument()
})
it('does not render collapse toggle when collapsible is false', () => {
render()
expect(screen.queryByText('Collapse')).not.toBeInTheDocument()
})
it('collapses when toggle button is clicked', () => {
const { container } = render(
)
fireEvent.click(screen.getByText('Collapse'))
// After collapse, the sidebar should have w-20 class
expect(container.firstChild).toHaveClass('w-20')
})
it('expands back when toggle is clicked again', () => {
const { container } = render(
)
// Collapse
fireEvent.click(screen.getByText('Collapse'))
// Expand — the chevron button is still present, click it
const expandBtn = screen.getByRole('button')
fireEvent.click(expandBtn)
expect(container.firstChild).toHaveClass('w-64')
})
it('hides link labels when collapsed', () => {
render(
)
// Labels are wrapped in a that only renders when not collapsed
expect(screen.queryByText('Dashboard')).not.toBeInTheDocument()
expect(screen.queryByText('Contacts')).not.toBeInTheDocument()
})
it('hides section titles when collapsed', () => {
render(
)
expect(screen.queryByText('Main')).not.toBeInTheDocument()
})
it('shows abbreviated app name when collapsed', () => {
render(
)
expect(screen.getByText('MA')).toBeInTheDocument()
})
it('renders user dropdown when provided', () => {
render(
User Menu}
/>
)
expect(screen.getByTestId('user-menu')).toBeInTheDocument()
})
it('applies custom className', () => {
const { container } = render(
)
expect(container.firstChild).toHaveClass('my-sidebar')
})
it('active link uses primary background (pathname matches)', () => {
// The mock usePathname returns '/test-path'
// We add a link with that href
const customSections = [
{
links: [
{ href: '/test-path', label: 'Active Link' },
{ href: '/other', label: 'Inactive Link' },
],
},
]
render()
const activeLink = screen.getByText('Active Link').closest('a')
// Semantic token, not a numeric ramp — `bg-primary-600` is undefined in apps
// that only declare --color-primary, so the active pill vanished (startsim-1f3n).
expect(activeLink).toHaveClass('bg-primary')
expect(activeLink).toHaveClass('text-primary-foreground')
})
it('inactive link uses gray text', () => {
const customSections = [
{
links: [
{ href: '/test-path', label: 'Active Link' },
{ href: '/other', label: 'Inactive Link' },
],
},
]
render()
const inactiveLink = screen.getByText('Inactive Link').closest('a')
expect(inactiveLink).toHaveClass('text-gray-300')
})
it('root href "/" is active only for exact match', () => {
// usePathname returns '/test-path', so '/' should not be active
const customSections = [
{
links: [{ href: '/', label: 'Home' }],
},
]
render()
const homeLink = screen.getByText('Home').closest('a')
expect(homeLink).not.toHaveClass('bg-primary')
})
})
// ---------------------------------------------------------------------------
// SidebarLayout
// ---------------------------------------------------------------------------
describe('SidebarLayout', () => {
it('renders children', () => {
render(
Page content
)
expect(screen.getByText('Page content')).toBeInTheDocument()
})
it('applies expanded width spacer by default', () => {
const { container } = render(
Content
)
const spacer = container.querySelector('.w-64')
expect(spacer).toBeInTheDocument()
})
it('applies collapsed width spacer when collapsed is true', () => {
const { container } = render(
Content
)
const spacer = container.querySelector('.w-20')
expect(spacer).toBeInTheDocument()
})
})