All files / src/components/common WidgetErrorBoundary.tsx

100% Statements 16/16
92.3% Branches 12/13
100% Functions 5/5
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159                                                                                                                                                                                            690x 690x             50x             25x     25x           25x 2x             1x 1x       1256x 1256x   1256x   50x 4x       46x                       1206x          
import React, { Component, ReactNode } from 'react';
import ErrorMessage from './ErrorMessage';
import logger from '../../utils/logger';
 
/**
 * Props for WidgetErrorBoundary component
 */
export interface WidgetErrorBoundaryProps {
  /**
   * Child components to wrap with error boundary
   */
  children: ReactNode;
  
  /**
   * Optional custom fallback component to display on error
   */
  fallback?: ReactNode;
  
  /**
   * Optional callback when an error is caught
   */
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
  
  /**
   * Optional widget name for error messages
   */
  widgetName?: string;
  
  /**
   * Optional test ID for automated testing
   */
  testId?: string;
}
 
/**
 * State for WidgetErrorBoundary component
 */
interface WidgetErrorBoundaryState {
  hasError: boolean;
  error?: Error;
}
 
/**
 * Error boundary component for wrapping widgets
 * 
 * ## Business Perspective
 * 
 * Prevents widget failures from crashing the entire application, ensuring
 * users can continue working even when individual components encounter errors.
 * Critical for maintaining operational continuity and user trust. 🛡️
 * 
 * ## Technical Perspective
 * 
 * React Error Boundary that catches JavaScript errors in child components,
 * logs them, and displays a fallback UI. Implements the error boundary
 * lifecycle methods to gracefully handle rendering errors.
 * 
 * Per React best practices, error boundaries catch errors during:
 * - Rendering
 * - Lifecycle methods
 * - Constructors of child components
 * 
 * They do NOT catch errors in:
 * - Event handlers (use try-catch)
 * - Asynchronous code (use try-catch)
 * - Server-side rendering
 * - Errors in the error boundary itself
 * 
 * @example
 * ```tsx
 * // Basic usage
 * <WidgetErrorBoundary>
 *   <SecurityMetricsWidget />
 * </WidgetErrorBoundary>
 * 
 * // With custom fallback
 * <WidgetErrorBoundary fallback={<CustomErrorUI />}>
 *   <ComplianceWidget />
 * </WidgetErrorBoundary>
 * 
 * // With error callback and widget name
 * <WidgetErrorBoundary 
 *   widgetName="Security Metrics"
 *   onError={(error, info) => logError(error, info)}
 * >
 *   <SecurityMetricsWidget />
 * </WidgetErrorBoundary>
 * ```
 */
export class WidgetErrorBoundary extends Component<
  WidgetErrorBoundaryProps,
  WidgetErrorBoundaryState
> {
  constructor(props: WidgetErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }
 
  /**
   * Update state when an error is caught
   */
  static getDerivedStateFromError(error: Error): WidgetErrorBoundaryState {
    return { hasError: true, error };
  }
 
  /**
   * Log error information and call optional callback
   */
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
    const { widgetName, onError } = this.props;
    
    // Log using centralized logger for debugging
    logger.error(
      `WidgetErrorBoundary caught error${widgetName ? ` in ${widgetName}` : ''}`,
      { error, errorInfo }
    );
    
    // Call optional error callback
    if (onError) {
      onError(error, errorInfo);
    }
  }
 
  /**
   * Reset error state (for retry functionality)
   */
  private resetError = (): void => {
    this.setState({ hasError: false, error: undefined });
  };
 
  render(): ReactNode {
    const { hasError, error } = this.state;
    const { children, fallback, widgetName, testId = 'widget-error-boundary' } = this.props;
 
    if (hasError) {
      // Use custom fallback if provided
      if (fallback) {
        return fallback;
      }
 
      // Default error UI using ErrorMessage component
      return (
        <div data-testid={testId} className="p-4">
          <ErrorMessage
            title={widgetName ? `${widgetName} Error` : 'Widget Error'}
            message={error?.message || 'An unexpected error occurred in this widget'}
            retry={this.resetError}
            testId={`${testId}-message`}
          />
        </div>
      );
    }
 
    return children;
  }
}
 
export default WidgetErrorBoundary;