glam/frontend/tests/unit/graph-context.test.tsx
kempersc 2761857b0d Add scripts for converting OWL/Turtle ontology to Mermaid and PlantUML diagrams
- Implemented `owl_to_mermaid.py` to convert OWL/Turtle files into Mermaid class diagrams.
- Implemented `owl_to_plantuml.py` to convert OWL/Turtle files into PlantUML class diagrams.
- Added two new PlantUML files for custodian multi-aspect diagrams.
2025-11-22 23:01:13 +01:00

331 lines
9.8 KiB
TypeScript

/**
* 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 <GraphProvider>{children}</GraphProvider>;
};
}
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,
});
});
});
});