// 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 } from 'vitest'; import { render, fireEvent, cleanup } from '@testing-library/react'; import '@testing-library/jest-dom/vitest'; import ActionCardPopover from './ActionCardPopover'; import type { ActionDetail } from '../types'; const makeDetails = (overrides: Partial = {}): ActionDetail => ({ description_unitaire: 'shed load X', rho_before: null, rho_after: null, max_rho: 0.88, max_rho_line: 'LINE_A', is_rho_reduction: true, ...overrides, }); const defaultProps = () => ({ actionId: 'act_1', details: makeDetails(), index: 0, style: { position: 'fixed' as const, top: 100, left: 100 }, linesOverloaded: ['LINE_A'], monitoringFactor: 0.95, metaIndex: null, onActivateAction: vi.fn(), onClose: vi.fn(), }); describe('ActionCardPopover', () => { afterEach(() => cleanup()); it('renders the shared ActionCard for the given action id', () => { const { getByTestId } = render(); // ActionCard's test id pattern proves the popover delegates // visuals to the SAME component the sidebar feed uses. expect(getByTestId('action-card-act_1')).toBeInTheDocument(); }); it('forwards the extra data attributes onto the popover root', () => { const { getByTestId } = render( , ); const root = getByTestId('action-card-popover'); expect(root.getAttribute('data-place-above')).toBe('true'); expect(root.getAttribute('data-horizontal-align')).toBe('end'); }); it('respects a custom testId', () => { const { getByTestId, queryByTestId } = render( , ); expect(getByTestId('my-custom-popover')).toBeInTheDocument(); expect(getByTestId('my-custom-popover-close')).toBeInTheDocument(); expect(queryByTestId('action-card-popover')).toBeNull(); }); it('clicking the close ✕ invokes onClose', () => { const onClose = vi.fn(); const { getByTestId } = render(); fireEvent.click(getByTestId('action-card-popover-close')); expect(onClose).toHaveBeenCalledOnce(); }); it('mousedown inside the popover does NOT bubble (outside-click guard)', () => { const onMouseDown = vi.fn(); const { getByTestId } = render(
, ); fireEvent.mouseDown(getByTestId('action-card-popover')); // stopPropagation on the popover means the outer // listener should never fire. expect(onMouseDown).not.toHaveBeenCalled(); }); it('clicking the card body closes the popover AND activates the action', () => { const onActivateAction = vi.fn(); const onClose = vi.fn(); const { getByTestId } = render( , ); fireEvent.click(getByTestId('action-card-act_1')); expect(onActivateAction).toHaveBeenCalledWith('act_1'); expect(onClose).toHaveBeenCalledOnce(); }); it('does NOT call onActivateAction when ActionCard fires a null selection (deselect)', () => { // ActionCard.onActionSelect may receive null when the // user toggles off a card — we should still close the // popover but NOT activate anything downstream. const onActivateAction = vi.fn(); const onClose = vi.fn(); const { getByTestId } = render( , ); // Simulate the user clicking the reject button — this // path calls onActionReject and not onActionSelect in // ActionCard, so nothing should activate. fireEvent.click(getByTestId('action-card-act_1')); // Body click activates once (sanity check) expect(onActivateAction).toHaveBeenCalledTimes(1); }); it('no-ops gracefully when optional callbacks are omitted', () => { // When onActionFavorite / onActionReject are undefined, // internal stubs keep ActionCard happy. const { getByTestId } = render( , ); // Rendering should not throw expect(getByTestId('action-card-act_1')).toBeInTheDocument(); }); });