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