/**
* Unit tests for HelpButton component
*
* Tests cover:
* - Component rendering
* - Link navigation
* - Icon display
* - Text display and responsive behavior
* - Accessibility attributes
* - Custom className prop
* - Internationalization (i18n)
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import React from 'react';
import HelpButton from '../HelpButton';
// Mock react-i18next
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, fallback: string) => fallback,
}),
}));
// Test wrapper with Router
const createWrapper = () => {
return ({ children }: { children: React.ReactNode }) => (
{children}
);
};
describe('HelpButton', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Rendering', () => {
it('should render the button', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
});
it('should render as a Link component with correct href', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/resources');
});
it('should render with different help paths', () => {
const { rerender } = render(, {
wrapper: createWrapper(),
});
let link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/page1');
rerender();
link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/page2');
});
});
describe('Icon Display', () => {
it('should display the HelpCircle icon', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
// Check for SVG icon (lucide-react renders as SVG)
const svg = link.querySelector('svg');
expect(svg).toBeInTheDocument();
});
});
describe('Text Display', () => {
it('should display help text', () => {
render(, {
wrapper: createWrapper(),
});
const text = screen.getByText('Help');
expect(text).toBeInTheDocument();
});
it('should apply responsive class to hide text on small screens', () => {
render(, {
wrapper: createWrapper(),
});
const text = screen.getByText('Help');
expect(text).toHaveClass('hidden', 'sm:inline');
});
});
describe('Accessibility', () => {
it('should have title attribute', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('title', 'Help');
});
it('should be keyboard accessible as a link', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
expect(link.tagName).toBe('A');
});
it('should have accessible name from text content', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link', { name: /help/i });
expect(link).toBeInTheDocument();
});
});
describe('Styling', () => {
it('should apply default classes', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('inline-flex');
expect(link).toHaveClass('items-center');
expect(link).toHaveClass('gap-1.5');
expect(link).toHaveClass('px-3');
expect(link).toHaveClass('py-1.5');
expect(link).toHaveClass('text-sm');
expect(link).toHaveClass('rounded-lg');
expect(link).toHaveClass('transition-colors');
});
it('should apply color classes for light mode', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('text-gray-500');
expect(link).toHaveClass('hover:text-brand-600');
expect(link).toHaveClass('hover:bg-gray-100');
});
it('should apply color classes for dark mode', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('dark:text-gray-400');
expect(link).toHaveClass('dark:hover:text-brand-400');
expect(link).toHaveClass('dark:hover:bg-gray-800');
});
it('should apply custom className when provided', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('custom-class');
});
it('should merge custom className with default classes', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('ml-auto');
expect(link).toHaveClass('inline-flex');
expect(link).toHaveClass('items-center');
});
it('should work without custom className', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
});
});
describe('Internationalization', () => {
it('should use translation for help text', () => {
render(, {
wrapper: createWrapper(),
});
// The mock returns the fallback value
const text = screen.getByText('Help');
expect(text).toBeInTheDocument();
});
it('should use translation for title attribute', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('title', 'Help');
});
});
describe('Integration', () => {
it('should render correctly with all props together', () => {
render(
,
{ wrapper: createWrapper() }
);
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', '/help/advanced');
expect(link).toHaveAttribute('title', 'Help');
expect(link).toHaveClass('custom-styling');
expect(link).toHaveClass('inline-flex');
const icon = link.querySelector('svg');
expect(icon).toBeInTheDocument();
const text = screen.getByText('Help');
expect(text).toBeInTheDocument();
});
it('should maintain structure with icon and text', () => {
render(, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
const svg = link.querySelector('svg');
const span = link.querySelector('span');
expect(svg).toBeInTheDocument();
expect(span).toBeInTheDocument();
expect(span).toHaveTextContent('Help');
});
});
});