// This program has been developed by students from the bachelor Computer Science at Utrecht // University within the Software Project course. // © Copyright Utrecht University (Department of Information and Computing Sciences) import {fireEvent, render, screen} from '@testing-library/react'; import '@testing-library/jest-dom'; import {useReactFlow, useStoreApi} from "@xyflow/react"; import { type EditorWarning, globalWarning } from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/EditorWarnings.tsx"; import {WarningsSidebar} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx"; import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx"; jest.mock('@xyflow/react', () => ({ useReactFlow: jest.fn(), useStoreApi: jest.fn(), })); jest.mock('../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx'); function makeWarning( overrides?: Partial ): EditorWarning { return { scope: { id: 'node-1' }, type: 'MISSING_INPUT', severity: 'ERROR', description: 'Missing input', ...overrides, }; } describe('WarningsSidebar', () => { let getStateSpy: jest.SpyInstance; const setCenter = jest.fn(() => Promise.resolve()); const getNode = jest.fn(); const addSelectedNodes = jest.fn(); beforeEach(() => { jest.clearAllMocks(); // React Flow hooks (useReactFlow as jest.Mock).mockReturnValue({ getNode, setCenter, }); (useStoreApi as jest.Mock).mockReturnValue({ getState: () => ({ addSelectedNodes }), }); // Use spyOn to override store const mockWarnings = [ makeWarning({ description: 'Node warning', scope: { id: 'node-1' } }), makeWarning({ description: 'Global warning', scope: { id: globalWarning }, type: 'INCOMPLETE_PROGRAM', severity: 'WARNING', }), makeWarning({ description: 'Info warning', scope: { id: 'node-2' }, severity: 'INFO', }), ]; getStateSpy = jest .spyOn(useFlowStore, 'getState') .mockReturnValue({ getWarnings: () => mockWarnings, } as any); }); afterEach(() => { getStateSpy.mockRestore(); }); it('renders warnings header', () => { render(); expect(screen.getByText('Warnings')).toBeInTheDocument(); }); it('renders all warning descriptions', () => { render(); expect(screen.getByText('Node warning')).toBeInTheDocument(); expect(screen.getByText('Global warning')).toBeInTheDocument(); expect(screen.getByText('Info warning')).toBeInTheDocument(); }); it('splits global and other warnings correctly', () => { render(); expect(screen.getByText('global:')).toBeInTheDocument(); expect(screen.getByText('other:')).toBeInTheDocument(); }); it('shows empty state when no warnings exist', () => { getStateSpy.mockReturnValueOnce({ getWarnings: () => [], } as any); render(); expect(screen.getByText('No warnings!')).toBeInTheDocument(); }); it('filters by severity', () => { render(); fireEvent.click(screen.getByText('ERROR')); expect(screen.getByText('Node warning')).toBeInTheDocument(); expect(screen.queryByText('Global warning')).not.toBeInTheDocument(); expect(screen.queryByText('Info warning')).not.toBeInTheDocument(); }); it('filters INFO severity correctly', () => { render(); fireEvent.click(screen.getByText('INFO')); expect(screen.getByText('Info warning')).toBeInTheDocument(); expect(screen.queryByText('Node warning')).not.toBeInTheDocument(); expect(screen.queryByText('Global warning')).not.toBeInTheDocument(); }); it('clicking global warning does NOT jump', () => { render(); fireEvent.click(screen.getByText('Global warning')); expect(setCenter).not.toHaveBeenCalled(); expect(addSelectedNodes).not.toHaveBeenCalled(); }); it('does nothing if node does not exist', () => { getNode.mockReturnValue(undefined); render(); fireEvent.click(screen.getByText('Node warning')); expect(setCenter).not.toHaveBeenCalled(); }); });