- 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>
265 lines
7.5 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|