// 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 { useState } from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import Header from './Header'; import type { AnalysisResult } from '../types'; describe('Header', () => { beforeEach(() => { vi.clearAllMocks(); }); const defaultProps = { networkPath: '/path/to/network.xiidm', setNetworkPath: vi.fn(), onCommitNetworkPath: vi.fn(), configLoading: false, result: null, selectedContingency: [], sessionRestoring: false, onPickSettingsPath: vi.fn(), onLoadStudy: vi.fn(), onSaveResults: vi.fn(), onOpenReloadModal: vi.fn(), onOpenSettings: vi.fn(), }; it('renders correctly with default props', () => { render(
); expect(screen.getByText('⚡ Co-Study4Grid')).toBeInTheDocument(); expect(screen.getByDisplayValue('/path/to/network.xiidm')).toBeInTheDocument(); expect(screen.getByText('🔄 Load Study')).toBeInTheDocument(); }); it('shows loading state on Load Study button', () => { render(
); expect(screen.getByText('⏳ Loading...')).toBeInTheDocument(); expect(screen.getByText('⏳ Loading...')).toBeDisabled(); }); it('disables Save Results when no result or selected branch', () => { render(
); expect(screen.getByText('💾 Save Results')).toBeDisabled(); }); it('enables Save Results when result is present', () => { render(
); // result present expect(screen.getByText('💾 Save Results')).not.toBeDisabled(); }); it('calls callbacks when buttons are clicked', () => { render(
); fireEvent.click(screen.getByText('🔄 Load Study')); expect(defaultProps.onLoadStudy).toHaveBeenCalled(); fireEvent.click(screen.getByText('Reload Session')); expect(defaultProps.onOpenReloadModal).toHaveBeenCalled(); fireEvent.click(screen.getByTitle('Settings')); expect(defaultProps.onOpenSettings).toHaveBeenCalledWith('paths'); }); it('calls onPickSettingsPath when file icon is clicked', () => { render(
); fireEvent.click(screen.getByText('📄')); expect(defaultProps.onPickSettingsPath).toHaveBeenCalled(); }); // The picker must commit through onCommitNetworkPath (App's // confirmation pipeline) rather than calling setNetworkPath // directly, so that picking a different file while a study is // already loaded triggers the confirmation dialog. it('routes the file picker through onCommitNetworkPath', () => { const onCommitNetworkPath = vi.fn(); const onPickSettingsPath = vi.fn(); render(
, ); fireEvent.click(screen.getByText('📄')); expect(onPickSettingsPath).toHaveBeenCalledTimes(1); // Second positional argument is the setter the picker will // call with the chosen path; it must be onCommitNetworkPath. expect(onPickSettingsPath.mock.calls[0][0]).toBe('file'); expect(onPickSettingsPath.mock.calls[0][1]).toBe(onCommitNetworkPath); }); // Blurring the input after editing the path also goes through // onCommitNetworkPath so the dialog fires for users who type a // path manually rather than using the picker. The Header uses a // controlled input so we wrap it in a stateful harness — without // it, fireEvent.change would be reverted by React's reconciliation // and the blur callback would still see the old value. it('calls onCommitNetworkPath when the network path input loses focus', () => { const onCommitNetworkPath = vi.fn(); const Harness = () => { const [networkPath, setNetworkPath] = useState('/old/path.xiidm'); return (
); }; render(); const input = screen.getByTestId('header-network-path-input') as HTMLInputElement; fireEvent.change(input, { target: { value: '/new/path.xiidm' } }); fireEvent.blur(input); expect(onCommitNetworkPath).toHaveBeenCalledWith('/new/path.xiidm'); }); describe('dark-mode toggle', () => { beforeEach(() => { localStorage.clear(); document.documentElement.removeAttribute('data-theme'); document.documentElement.style.colorScheme = ''; window.matchMedia = vi.fn().mockImplementation((query: string) => ({ matches: false, media: query, onchange: null, addEventListener: vi.fn(), removeEventListener: vi.fn(), addListener: vi.fn(), removeListener: vi.fn(), dispatchEvent: vi.fn(), })); }); it('renders a theme toggle that shows the moon glyph in light mode', () => { render(
); const toggle = screen.getByTestId('header-theme-toggle'); expect(toggle).toHaveTextContent('☾'); expect(toggle).toHaveAttribute('aria-label', 'Switch to dark mode'); }); it('flips the document theme and glyph when clicked', () => { render(
); const toggle = screen.getByTestId('header-theme-toggle'); fireEvent.click(toggle); expect(document.documentElement.getAttribute('data-theme')).toBe('dark'); expect(toggle).toHaveTextContent('☀'); expect(toggle).toHaveAttribute('aria-label', 'Switch to light mode'); fireEvent.click(toggle); expect(document.documentElement.getAttribute('data-theme')).toBe('light'); expect(toggle).toHaveTextContent('☾'); }); }); });