/** * Unit tests for GraphContext */ import { renderHook, act } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { GraphProvider, useGraph } from '../../src/contexts/GraphContext'; import type { GraphData, GraphNode, GraphLink } from '../../src/types/graph'; // ============================================================================ // Test Utilities // ============================================================================ function createWrapper() { return function Wrapper({ children }: { children: React.ReactNode }) { return {children}; }; } const sampleGraphData: GraphData = { nodes: [ { id: 'node1', label: 'Node 1', type: 'Resource', uri: 'http://example.org/node1' }, { id: 'node2', label: 'Node 2', type: 'Class', uri: 'http://example.org/node2' }, ], links: [ { source: 'node1', target: 'node2', predicate: 'relatesTo', isBidirectional: false, }, ], }; const sampleNode: GraphNode = sampleGraphData.nodes[0]; const sampleLink: GraphLink = sampleGraphData.links[0]; // ============================================================================ // Tests // ============================================================================ describe('GraphContext', () => { describe('Provider and Hook', () => { it('should throw error when useGraph is used outside provider', () => { expect(() => { renderHook(() => useGraph()); }).toThrow('useGraph must be used within GraphProvider'); }); it('should provide initial state', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); expect(result.current.state.graphData).toBe(null); expect(result.current.state.isLoading).toBe(false); expect(result.current.state.error).toBe(null); expect(result.current.state.selectedNode).toBe(null); expect(result.current.state.selectedLink).toBe(null); expect(result.current.state.filters).toEqual({ nodeTypes: [], predicates: [], searchQuery: '', }); expect(result.current.state.config).toEqual({ showLabels: true, nodeSize: 8, linkWidth: 1.5, chargeStrength: -300, linkDistance: 100, }); }); }); describe('Graph Data Management', () => { it('should set graph data', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.setGraphData(sampleGraphData); }); expect(result.current.state.graphData).toEqual(sampleGraphData); expect(result.current.state.isLoading).toBe(false); expect(result.current.state.error).toBe(null); }); it('should set loading state', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.setLoading(true); }); expect(result.current.state.isLoading).toBe(true); act(() => { result.current.actions.setLoading(false); }); expect(result.current.state.isLoading).toBe(false); }); it('should set error', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.setError('Test error'); }); expect(result.current.state.error).toBe('Test error'); expect(result.current.state.isLoading).toBe(false); act(() => { result.current.actions.setError(null); }); expect(result.current.state.error).toBe(null); }); }); describe('Selection Management', () => { it('should select node', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.selectNode(sampleNode); }); expect(result.current.state.selectedNode).toEqual(sampleNode); expect(result.current.state.selectedLink).toBe(null); // Link should be cleared }); it('should select link', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); // First select a node act(() => { result.current.actions.selectNode(sampleNode); }); expect(result.current.state.selectedNode).toEqual(sampleNode); // Then select a link (should clear node) act(() => { result.current.actions.selectLink(sampleLink); }); expect(result.current.state.selectedLink).toEqual(sampleLink); expect(result.current.state.selectedNode).toBe(null); // Node should be cleared }); it('should clear selection', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); // Select node and link act(() => { result.current.actions.selectNode(sampleNode); result.current.actions.selectLink(sampleLink); }); // Clear selection act(() => { result.current.actions.clearSelection(); }); expect(result.current.state.selectedNode).toBe(null); expect(result.current.state.selectedLink).toBe(null); }); }); describe('Filter Management', () => { it('should update filters partially', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.updateFilters({ nodeTypes: ['Resource', 'Class'] }); }); expect(result.current.state.filters.nodeTypes).toEqual(['Resource', 'Class']); expect(result.current.state.filters.predicates).toEqual([]); // Should remain unchanged expect(result.current.state.filters.searchQuery).toBe(''); // Should remain unchanged }); it('should update multiple filters at once', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.updateFilters({ nodeTypes: ['Resource'], predicates: ['relatesTo'], searchQuery: 'test', }); }); expect(result.current.state.filters).toEqual({ nodeTypes: ['Resource'], predicates: ['relatesTo'], searchQuery: 'test', }); }); it('should reset filters', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); // Set some filters act(() => { result.current.actions.updateFilters({ nodeTypes: ['Resource'], predicates: ['relatesTo'], searchQuery: 'test', }); }); expect(result.current.state.filters).not.toEqual({ nodeTypes: [], predicates: [], searchQuery: '', }); // Reset filters act(() => { result.current.actions.resetFilters(); }); expect(result.current.state.filters).toEqual({ nodeTypes: [], predicates: [], searchQuery: '', }); }); }); describe('Visualization Config Management', () => { it('should update config partially', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.updateConfig({ nodeSize: 12 }); }); expect(result.current.state.config.nodeSize).toBe(12); expect(result.current.state.config.showLabels).toBe(true); // Should remain unchanged }); it('should update multiple config options at once', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); act(() => { result.current.actions.updateConfig({ showLabels: false, nodeSize: 10, linkWidth: 2, }); }); expect(result.current.state.config.showLabels).toBe(false); expect(result.current.state.config.nodeSize).toBe(10); expect(result.current.state.config.linkWidth).toBe(2); expect(result.current.state.config.chargeStrength).toBe(-300); // Should remain unchanged }); }); describe('Clear All', () => { it('should reset to initial state', () => { const { result } = renderHook(() => useGraph(), { wrapper: createWrapper(), }); // Set various state act(() => { result.current.actions.setGraphData(sampleGraphData); result.current.actions.selectNode(sampleNode); result.current.actions.updateFilters({ searchQuery: 'test' }); result.current.actions.updateConfig({ nodeSize: 10 }); }); // Verify state was set expect(result.current.state.graphData).not.toBe(null); expect(result.current.state.selectedNode).not.toBe(null); expect(result.current.state.filters.searchQuery).toBe('test'); expect(result.current.state.config.nodeSize).toBe(10); // Clear all act(() => { result.current.actions.clearAll(); }); // Verify reset to initial state expect(result.current.state.graphData).toBe(null); expect(result.current.state.isLoading).toBe(false); expect(result.current.state.error).toBe(null); expect(result.current.state.selectedNode).toBe(null); expect(result.current.state.selectedLink).toBe(null); expect(result.current.state.filters).toEqual({ nodeTypes: [], predicates: [], searchQuery: '', }); expect(result.current.state.config).toEqual({ showLabels: true, nodeSize: 8, linkWidth: 1.5, chargeStrength: -300, linkDistance: 100, }); }); }); });