import { render, screen } from '@testing-library/react'
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'
import { ErrorBoundary } from '@/components/ErrorBoundary'
import React from 'react'
// Component that throws an error for testing
const ThrowError: React.FC<{ shouldThrow: boolean }> = ({ shouldThrow }) => {
if (shouldThrow) {
throw new Error('Test error message')
}
return
No error
}
// Mock console.error to suppress error output in tests
const originalError = console.error
beforeEach(() => {
console.error = vi.fn()
})
afterEach(() => {
console.error = originalError
})
describe('ErrorBoundary Component', () => {
test('renders children when there is no error', () => {
render(
Test content
)
expect(screen.getByText('Test content')).toBeInTheDocument()
})
test('catches errors and displays fallback UI', () => {
render(
)
// Should show error fallback
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument()
expect(screen.queryByText('No error')).not.toBeInTheDocument()
})
test('displays custom error fallback when provided', () => {
const CustomFallback = ({ error }: { error: Error }) => (
Custom error: {error.message}
)
render(
)
expect(screen.getByText('Custom error: Test error message')).toBeInTheDocument()
})
test('renders different UI for page-level errors', () => {
render(
)
// Page-level errors should have specific styling
const errorContainer = screen.getByText(/Something went wrong/i).closest('div')
expect(errorContainer?.className).toContain('min-h-screen')
})
test('renders different UI for component-level errors', () => {
render(
)
// Component-level errors should have different styling
const errorContainer = screen.getByText(/Something went wrong/i).closest('div')
expect(errorContainer?.className).not.toContain('min-h-screen')
expect(errorContainer?.className).toContain('rounded-lg')
})
test('passes error object to error fallback', () => {
const error = new Error('Specific error message')
const CustomFallback = ({ error: err }: { error: Error }) => (
Error occurred
{err.message}
)
render(
)
expect(screen.getByText('Error occurred')).toBeInTheDocument()
expect(screen.getByText('Test error message')).toBeInTheDocument()
})
test('handles multiple error boundaries at different levels', () => {
const OuterFallback = () => Outer error
const InnerFallback = () => Inner error
render(
)
// Inner boundary should catch the error
expect(screen.getByText('Inner error')).toBeInTheDocument()
expect(screen.queryByText('Outer error')).not.toBeInTheDocument()
})
test('recovers when error condition is resolved', () => {
const { rerender } = render(
)
// Error is shown
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument()
// When component no longer throws, it should recover
rerender(
)
// Note: React Error Boundaries don't automatically recover,
// so the error state persists. This is expected behavior.
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument()
})
test('logs errors to console in development', () => {
const consoleErrorSpy = vi.spyOn(console, 'error')
render(
)
// Error should be logged
expect(consoleErrorSpy).toHaveBeenCalled()
})
test('renders with suspense wrapper when specified', () => {
// Testing SuspenseErrorBoundary variant
const LazyComponent = React.lazy(() =>
Promise.resolve({ default: () => Lazy loaded
})
)
render(
Loading...}>
)
// Should show loading initially
expect(screen.getByText('Loading...')).toBeInTheDocument()
})
})