/* eslint-disable import-x/order */
import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
// Mock lucide-react icons to avoid SVG rendering issues
vi.mock('lucide-react', () => ({
ChevronDown: () => ,
LayoutDashboard: () => ,
LogOut: () => ,
Menu: () => ,
Moon: () => ,
Shield: () => ,
Sun: () => ,
User: () => ,
X: () => ,
}));
import { Navbar, type NavbarProps } from '../navbar';
const renderLink: NavbarProps['renderLink'] = ({ href, children, className, onClick }) => (
{children}
);
const baseLabels: NavbarProps['labels'] = {
login: 'Đăng nhập',
register: 'Đăng ký',
dashboard: 'Quản lý',
admin: 'Quản trị',
profile: 'Hồ sơ',
logout: 'Đăng xuất',
openMenu: 'Mở menu',
closeMenu: 'Đóng menu',
darkMode: 'Chế độ tối',
lightMode: 'Chế độ sáng',
mainNav: 'Điều hướng chính',
};
const baseLinks: NavbarProps['links'] = [
{ href: '/', label: 'Trang chủ', isActive: true },
{ href: '/search', label: 'Tìm kiếm', isActive: false },
{ href: '/pricing', label: 'Bảng giá', isActive: false },
];
const defaultProps: NavbarProps = {
brand: 'GoodGo',
links: baseLinks,
user: null,
dashboardHref: '/dashboard',
theme: 'light',
onToggleTheme: vi.fn(),
onLogout: vi.fn(),
labels: baseLabels,
renderLink,
};
describe('Navbar', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders the brand name', () => {
render();
expect(screen.getByText('GoodGo')).toBeInTheDocument();
});
it('renders as a banner landmark', () => {
render();
expect(screen.getByRole('banner')).toBeInTheDocument();
});
it('renders desktop nav links', () => {
render();
expect(screen.getAllByText('Trang chủ').length).toBeGreaterThan(0);
expect(screen.getAllByText('Tìm kiếm').length).toBeGreaterThan(0);
expect(screen.getAllByText('Bảng giá').length).toBeGreaterThan(0);
});
it('renders login and register buttons when unauthenticated', () => {
render();
expect(screen.getAllByText('Đăng nhập').length).toBeGreaterThan(0);
expect(screen.getAllByText('Đăng ký').length).toBeGreaterThan(0);
});
it('does not render dashboard button when unauthenticated', () => {
render();
expect(screen.queryByText('Quản lý')).toBeNull();
});
it('renders user full name when authenticated', () => {
render(
,
);
expect(screen.getAllByText('Nguyễn Văn A').length).toBeGreaterThan(0);
});
it('renders dashboard menu item for authenticated user (after opening dropdown)', () => {
render(
,
);
// The pill is the dropdown trigger; click it to reveal the menu.
const trigger = screen.getByRole('button', { name: /Nguyễn Văn A/ });
fireEvent.click(trigger);
expect(screen.getByRole('menuitem', { name: /Quản lý/ })).toBeInTheDocument();
});
it('renders admin label as a role badge AND in the dropdown for ADMIN role', () => {
render(
,
);
// Role badge in the trigger pill is always visible.
expect(screen.getByText('Quản trị viên')).toBeInTheDocument();
// After opening, the ADMIN-specific menu item shows.
fireEvent.click(screen.getByRole('button', { name: /Admin User/ }));
expect(screen.getByRole('menuitem', { name: /Quản trị/ })).toBeInTheDocument();
});
it('shows moon icon in light theme', () => {
render();
expect(screen.getByTestId('icon-moon')).toBeInTheDocument();
});
it('shows sun icon in dark theme', () => {
render();
expect(screen.getByTestId('icon-sun')).toBeInTheDocument();
});
it('calls onToggleTheme when theme button is clicked', () => {
const onToggleTheme = vi.fn();
render();
const themeBtn = screen.getByRole('button', { name: 'Chế độ tối' });
fireEvent.click(themeBtn);
expect(onToggleTheme).toHaveBeenCalledTimes(1);
});
it('toggles mobile menu on hamburger click', () => {
render();
const hamburger = screen.getByRole('button', { name: 'Mở menu' });
fireEvent.click(hamburger);
expect(screen.getByRole('button', { name: 'Đóng menu' })).toBeInTheDocument();
});
it('renders main nav accessible label', () => {
render();
const navEls = screen.getAllByRole('navigation');
const mainNavs = navEls.filter((el) => el.getAttribute('aria-label') === 'Điều hướng chính');
expect(mainNavs.length).toBeGreaterThan(0);
});
it('renders notification slot when provided', () => {
render(
🔔}
/>,
);
expect(screen.getByRole('button', { name: 'Thông báo' })).toBeInTheDocument();
});
it('renders language switcher slot when provided', () => {
render(
VI}
/>,
);
expect(screen.getByTestId('lang-sw')).toBeInTheDocument();
});
it('calls onLogout and closes mobile menu when logout clicked', async () => {
const onLogout = vi.fn().mockResolvedValue(undefined);
render(
,
);
// Open mobile menu
fireEvent.click(screen.getByRole('button', { name: 'Mở menu' }));
const logoutBtn = screen.getByRole('button', { name: 'Đăng xuất' });
fireEvent.click(logoutBtn);
expect(onLogout).toHaveBeenCalledTimes(1);
});
});