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                                                                                                                                                                                            564x 564x             50x             25x   25x         25x 2x             1x 1x       1010x 1010x   1010x 50x 4x     46x                       960x          
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;
    
    logger.error(
      `WidgetErrorBoundary caught error${widgetName ? ` in ${widgetName}` : ''}`,
      { error, errorInfo }
    );
    
    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) {
      if (fallback) {
        return fallback;
      }
 
      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;