import { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderWithProviders as render } from '../../../test/render';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Sidebar } from './sidebar';
import React from 'react';
// Mock framer-motion
vi.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) =>
{children}
,
button: ({ children, ...props }: any) => ,
},
AnimatePresence: ({ children }: any) => <>{children}>,
}));
describe('Sidebar', () => {
const defaultProps = {
expanded: true,
onToggle: vi.fn(),
user: { email: 'test@example.com' },
onLogout: vi.fn(),
location: { pathname: '/' },
navigate: vi.fn(),
routes: [
{ path: '/', label: 'Home', icon: () => },
{ path: '/dashboard', label: 'Dashboard', icon: () => },
],
};
beforeEach(() => {
vi.clearAllMocks();
// Mock clientHeight for overflow detection
Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
configurable: true,
value: 1000,
});
});
it('renders with local fallback state when no LayoutProvider is present', () => {
render();
expect(screen.getByLabelText('Expandir menu')).toBeInTheDocument();
});
it('renders correctly when expanded', async () => {
render();
// Sidebar also renders an aria-hidden MeasurementClone (off-screen copy of
// the nav items used to measure real heights for overflow detection), so
// queries must ignore it to avoid matching both copies of each label.
await waitFor(() => {
expect(screen.getByText('InĂcio', { ignore: '[aria-hidden="true"] *' })).toBeInTheDocument(); // Translated from Home
expect(screen.getByText('Painel', { ignore: '[aria-hidden="true"] *' })).toBeInTheDocument(); // Translated from Dashboard
});
});
it('calls onToggle when toggle button is clicked', () => {
render();
const toggleButton = screen.getByLabelText('Recolher menu');
fireEvent.click(toggleButton);
expect(defaultProps.onToggle).toHaveBeenCalled();
});
it('calls navigate when a menu item is clicked', async () => {
render();
// Ignore the aria-hidden MeasurementClone's duplicate copy of the label.
const dashboardLink = await screen.findByText('Painel', { ignore: '[aria-hidden="true"] *' });
fireEvent.click(dashboardLink);
expect(defaultProps.navigate).toHaveBeenCalledWith('/dashboard');
});
it('renders correctly in assistant variant', () => {
render(
);
expect(screen.getByPlaceholderText('Buscar conversas')).toBeInTheDocument();
});
it('renders the Exit Assistant button in assistant variant (expanded) and calls onExitAssistant on click', () => {
const onExitAssistant = vi.fn();
render();
fireEvent.click(screen.getByText('Sair do Assistente'));
expect(onExitAssistant).toHaveBeenCalledTimes(1);
});
it('does not render the Exit Assistant button in default variant, even if showExitAssistant is forced true', () => {
render();
expect(screen.queryByText('Sair do Assistente')).not.toBeInTheDocument();
expect(screen.queryByLabelText('Sair do Assistente')).not.toBeInTheDocument();
});
it('lets consumers opt out of the Exit Assistant button via showExitAssistant={false}', () => {
render();
expect(screen.queryByText('Sair do Assistente')).not.toBeInTheDocument();
expect(screen.queryByLabelText('Sair do Assistente')).not.toBeInTheDocument();
});
it('falls back to window.history.back() when onExitAssistant is not provided', () => {
const backSpy = vi.spyOn(window.history, 'back').mockImplementation(() => {});
render();
fireEvent.click(screen.getByText('Sair do Assistente'));
expect(backSpy).toHaveBeenCalledTimes(1);
backSpy.mockRestore();
});
it('renders the Exit Assistant button as icon-only with a label when collapsed', () => {
render();
expect(screen.getByLabelText('Sair do Assistente')).toBeInTheDocument();
expect(screen.getByLabelText('Expandir menu')).toBeInTheDocument();
});
it('regression: toggle button still calls onToggle and reflects aria-expanded once the Exit Assistant button is present', () => {
const onToggle = vi.fn();
const onExitAssistant = vi.fn();
render(
);
const toggleButton = screen.getByLabelText('Recolher menu');
expect(toggleButton).toHaveAttribute('aria-expanded', 'true');
fireEvent.click(toggleButton);
expect(onToggle).toHaveBeenCalledTimes(1);
expect(onExitAssistant).not.toHaveBeenCalled();
});
it('regression: Exit Assistant click does not trigger onToggle', () => {
const onToggle = vi.fn();
render(
);
fireEvent.click(screen.getByText('Sair do Assistente'));
expect(onToggle).not.toHaveBeenCalled();
});
});