Files
smoothschedule/frontend/src/components/__tests__/HelpButton.test.tsx
poduck 8dc2248f1f feat: Add comprehensive test suite and misc improvements
- Add frontend unit tests with Vitest for components, hooks, pages, and utilities
- Add backend tests for webhooks, notifications, middleware, and edge cases
- Add ForgotPassword, NotFound, and ResetPassword pages
- Add migration for orphaned staff resources conversion
- Add coverage directory to gitignore (generated reports)
- Various bug fixes and improvements from previous work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 02:36:46 -05:00

265 lines
7.5 KiB
TypeScript

/**
* 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 }) => (
<BrowserRouter>{children}</BrowserRouter>
);
};
describe('HelpButton', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Rendering', () => {
it('should render the button', () => {
render(<HelpButton helpPath="/help/getting-started" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
});
it('should render as a Link component with correct href', () => {
render(<HelpButton helpPath="/help/resources" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/resources');
});
it('should render with different help paths', () => {
const { rerender } = render(<HelpButton helpPath="/help/page1" />, {
wrapper: createWrapper(),
});
let link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/page1');
rerender(<HelpButton helpPath="/help/page2" />);
link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/help/page2');
});
});
describe('Icon Display', () => {
it('should display the HelpCircle icon', () => {
render(<HelpButton helpPath="/help" />, {
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(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const text = screen.getByText('Help');
expect(text).toBeInTheDocument();
});
it('should apply responsive class to hide text on small screens', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const text = screen.getByText('Help');
expect(text).toHaveClass('hidden', 'sm:inline');
});
});
describe('Accessibility', () => {
it('should have title attribute', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('title', 'Help');
});
it('should be keyboard accessible as a link', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
expect(link.tagName).toBe('A');
});
it('should have accessible name from text content', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link', { name: /help/i });
expect(link).toBeInTheDocument();
});
});
describe('Styling', () => {
it('should apply default classes', () => {
render(<HelpButton helpPath="/help" />, {
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(<HelpButton helpPath="/help" />, {
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(<HelpButton helpPath="/help" />, {
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(<HelpButton helpPath="/help" className="custom-class" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveClass('custom-class');
});
it('should merge custom className with default classes', () => {
render(<HelpButton helpPath="/help" className="ml-auto" />, {
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(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
});
});
describe('Internationalization', () => {
it('should use translation for help text', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
// The mock returns the fallback value
const text = screen.getByText('Help');
expect(text).toBeInTheDocument();
});
it('should use translation for title attribute', () => {
render(<HelpButton helpPath="/help" />, {
wrapper: createWrapper(),
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('title', 'Help');
});
});
describe('Integration', () => {
it('should render correctly with all props together', () => {
render(
<HelpButton
helpPath="/help/advanced"
className="custom-styling"
/>,
{ 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(<HelpButton helpPath="/help" />, {
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');
});
});
});