← back to Angels Flowers

components/__tests__/ThemeToggle.test.tsx

117 lines

import { render, screen } from '@/test-utils';
import ThemeToggle from '../ThemeToggle';
import { useTheme } from '../ThemeProvider';

// Mock the useTheme hook
jest.mock('../ThemeProvider', () => ({
  useTheme: jest.fn(),
}));

describe('ThemeToggle', () => {
  const mockToggleTheme = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('renders the toggle button', () => {
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'light',
      toggleTheme: mockToggleTheme,
    });

    render(<ThemeToggle />);

    const button = screen.getByRole('button');
    expect(button).toBeInTheDocument();
  });

  it('displays sun icon in dark mode', () => {
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'dark',
      toggleTheme: mockToggleTheme,
    });

    render(<ThemeToggle />);

    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('aria-label', 'Switch to light mode');
    expect(button).toHaveAttribute('aria-pressed', 'true');
  });

  it('displays moon icon in light mode', () => {
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'light',
      toggleTheme: mockToggleTheme,
    });

    render(<ThemeToggle />);

    const button = screen.getByRole('button');
    expect(button).toHaveAttribute('aria-label', 'Switch to dark mode');
    expect(button).toHaveAttribute('aria-pressed', 'false');
  });

  it('calls toggleTheme when clicked', () => {
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'light',
      toggleTheme: mockToggleTheme,
    });

    render(<ThemeToggle />);

    const button = screen.getByRole('button');
    button.click();

    expect(mockToggleTheme).toHaveBeenCalledTimes(1);
  });

  it('applies custom className', () => {
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'light',
      toggleTheme: mockToggleTheme,
    });

    render(<ThemeToggle className="custom-class" />);

    const button = screen.getByRole('button');
    expect(button).toHaveClass('custom-class');
  });

  it('updates icon when theme prop changes', () => {
    const { rerender } = render(<ThemeToggle />);

    // Start with light theme
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'light',
      toggleTheme: mockToggleTheme,
    });

    rerender(<ThemeToggle />);
    let button = screen.getByRole('button');
    expect(button).toHaveAttribute('aria-label', 'Switch to dark mode');
    expect(button).toHaveAttribute('aria-pressed', 'false');

    // Switch to dark theme
    (useTheme as jest.Mock).mockReturnValue({
      theme: 'dark',
      toggleTheme: mockToggleTheme,
    });

    rerender(<ThemeToggle />);
    button = screen.getByRole('button');
    expect(button).toHaveAttribute('aria-label', 'Switch to light mode');
    expect(button).toHaveAttribute('aria-pressed', 'true');
  });

  it('handles missing theme context gracefully', () => {
    // Mock useTheme to throw error (simulating missing provider)
    (useTheme as jest.Mock).mockImplementation(() => {
      throw new Error('useTheme must be used within a ThemeProvider');
    });

    // Expect the component to throw when rendered without provider
    expect(() => render(<ThemeToggle />)).toThrow('useTheme must be used within a ThemeProvider');
  });
});