diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx index 867a027..204a474 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx @@ -117,10 +117,6 @@ function WarningListItem(props: { warning: EditorWarning }) {
{props.warning.type} - {/*{props.warning.scope.id}*/} - {/*{props.warning.scope.handleId && (*/} - {/* @{props.warning.scope.handleId}*/} - {/*)}*/}
); diff --git a/test/pages/visProgPage/visualProgrammingUI/components/WarningSidebar.test.tsx b/test/pages/visProgPage/visualProgrammingUI/components/WarningSidebar.test.tsx new file mode 100644 index 0000000..19c3ecc --- /dev/null +++ b/test/pages/visProgPage/visualProgrammingUI/components/WarningSidebar.test.tsx @@ -0,0 +1,159 @@ +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 node warning jumps to node', async () => { + getNode.mockReturnValue({ + id: 'node-1', + position: { x: 100, y: 200 }, + width: 50, + height: 50, + }); + + render(); + fireEvent.click(screen.getByText('Node warning')); + + expect(setCenter).toHaveBeenCalledWith( + 125, + 225, + expect.objectContaining({ zoom: 2 }) + ); + + await Promise.resolve(); + expect(addSelectedNodes).toHaveBeenCalledWith(['node-1']); + }); + + 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(); + }); +}); \ No newline at end of file