// Copyright (c) 2025-2026, RTE (https://www.rte-france.com) // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, // you can obtain one at http://mozilla.org/MPL/2.0/. // SPDX-License-Identifier: MPL-2.0 // This file is part of Co-Study4Grid a Power Grid Study tool Assistant Interface to help solve contigencies for a grid state under study. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { useState } from 'react'; import ErrorBoundary from './ErrorBoundary'; /** Helper component that throws on render when `shouldThrow` is true. */ function Bomb({ shouldThrow, message = 'boom' }: { shouldThrow: boolean; message?: string }) { if (shouldThrow) { throw new Error(message); } return
child content
; } /** Wrapper that allows a test to flip the bomb from thrown -> healthy. */ function RecoverableBomb() { const [broken, setBroken] = useState(true); return ( ); } describe('ErrorBoundary', () => { let consoleErrorSpy: ReturnType; beforeEach(() => { // React logs caught errors to console.error; silence them to keep // test output clean. consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { consoleErrorSpy.mockRestore(); }); it('renders children when no error is thrown', () => { render( , ); expect(screen.getByText('child content')).toBeInTheDocument(); }); it('renders the default fallback UI when a child throws', () => { render( , ); expect(screen.getByRole('alert')).toBeInTheDocument(); expect(screen.getByText('Something went wrong')).toBeInTheDocument(); // Error message appears in both the details block and the stack trace. expect(screen.getAllByText(/kaboom/).length).toBeGreaterThan(0); expect(screen.getByRole('button', { name: 'Try again' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Reload page' })).toBeInTheDocument(); }); it('renders a custom fallback when provided', () => { render( custom fallback}> , ); expect(screen.getByText('custom fallback')).toBeInTheDocument(); expect(screen.queryByText('Something went wrong')).not.toBeInTheDocument(); }); it('logs the caught error to console.error', () => { render( , ); const loggedFromBoundary = consoleErrorSpy.mock.calls.some( (call) => typeof call[0] === 'string' && call[0].includes('ErrorBoundary caught an error'), ); expect(loggedFromBoundary).toBe(true); }); it('recovers when the user clicks "Try again" and the child no longer throws', () => { render(); // Initially in error state. expect(screen.getByText('Something went wrong')).toBeInTheDocument(); // The "fix" button is not in the DOM because the fallback replaced the tree. expect(screen.queryByRole('button', { name: 'fix' })).not.toBeInTheDocument(); // We can't fix state from inside the fallback, so this test just verifies // that "Try again" resets the boundary. The child will re-throw and the // fallback will remain. fireEvent.click(screen.getByRole('button', { name: 'Try again' })); // After reset + rethrow, still shows fallback. expect(screen.getByText('Something went wrong')).toBeInTheDocument(); }); it('calls window.location.reload when "Reload page" is clicked', () => { const reloadMock = vi.fn(); const originalLocation = window.location; Object.defineProperty(window, 'location', { configurable: true, value: { ...originalLocation, reload: reloadMock }, }); try { render( , ); fireEvent.click(screen.getByRole('button', { name: 'Reload page' })); expect(reloadMock).toHaveBeenCalledTimes(1); } finally { Object.defineProperty(window, 'location', { configurable: true, value: originalLocation, }); } }); it('shows error details in a collapsible
element', () => { render( , ); expect(screen.getByText('Error details')).toBeInTheDocument(); expect(screen.getAllByText(/detail-test/).length).toBeGreaterThan(0); }); });