- Refactored useFilteredNodes to memoize logic and prevent re-rendering. - Optimized LeftPanel useMemo dependencies and HostChart O(N) traversal. - Fixed polling interval desync and added WebSocket throttling/React Strict Mode transport patch. - Fixed Graph layout dependencies and 'ghost' children chevrons on collapsed nodes. - Fixed RightPanel tab persistence UI bug and resolved React Hooks order crash. No remaining known issues from the UI analysis.
114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import { describe, test, expect, afterEach } from 'vitest';
|
|
import { useTopologyStore } from './topologyStore';
|
|
import { TopologyNode } from '../types';
|
|
|
|
const mockNodes: TopologyNode[] = [
|
|
{
|
|
id: 'gateway-1',
|
|
name: 'Gateway',
|
|
type: 'gateway',
|
|
data: {
|
|
status: 'running',
|
|
ip: '10.0.0.1',
|
|
parentId: undefined,
|
|
importance: 5,
|
|
metadata: {},
|
|
},
|
|
},
|
|
{
|
|
id: 'host-1',
|
|
name: 'Ubuntu',
|
|
type: 'host_vm',
|
|
data: {
|
|
status: 'running',
|
|
ip: '10.0.0.10',
|
|
parentId: 'gateway-1',
|
|
importance: 4,
|
|
metadata: {},
|
|
},
|
|
},
|
|
{
|
|
id: 'service-1',
|
|
name: 'Traefik',
|
|
type: 'service',
|
|
data: {
|
|
status: 'running',
|
|
ip: '10.0.0.10',
|
|
parentId: 'host-1',
|
|
category: 'infra',
|
|
importance: 5,
|
|
metadata: {},
|
|
},
|
|
},
|
|
{
|
|
id: 'service-2',
|
|
name: 'Plex',
|
|
type: 'service',
|
|
data: {
|
|
status: 'stopped',
|
|
ip: '10.0.0.10',
|
|
parentId: 'host-1',
|
|
category: 'media',
|
|
importance: 2,
|
|
metadata: {},
|
|
},
|
|
},
|
|
];
|
|
|
|
describe('topologyStore', () => {
|
|
afterEach(() => {
|
|
// Reset store state between tests
|
|
useTopologyStore.setState({
|
|
nodes: [],
|
|
edges: [],
|
|
selectedNodeId: null,
|
|
searchQuery: '',
|
|
typeFilters: [],
|
|
statusFilter: 'all',
|
|
highlightPath: [],
|
|
});
|
|
});
|
|
|
|
/* ----------------------------------------------------------------
|
|
* Basic state management
|
|
* ------------------------------------------------------------- */
|
|
|
|
describe('setNodes', () => {
|
|
test('sets nodes correctly', () => {
|
|
useTopologyStore.getState().setNodes(mockNodes);
|
|
expect(useTopologyStore.getState().nodes).toHaveLength(4);
|
|
});
|
|
});
|
|
|
|
describe('setSelectedNode', () => {
|
|
test('selects a node by id', () => {
|
|
useTopologyStore.getState().setNodes(mockNodes);
|
|
useTopologyStore.getState().setSelectedNode('host-1');
|
|
expect(useTopologyStore.getState().selectedNodeId).toBe('host-1');
|
|
});
|
|
|
|
test('clears selection with null', () => {
|
|
useTopologyStore.getState().setSelectedNode('host-1');
|
|
useTopologyStore.getState().setSelectedNode(null);
|
|
expect(useTopologyStore.getState().selectedNodeId).toBeNull();
|
|
});
|
|
});
|
|
|
|
/* ----------------------------------------------------------------
|
|
* Type filter toggle
|
|
* ------------------------------------------------------------- */
|
|
|
|
describe('toggleTypeFilter', () => {
|
|
test('adds type when not present', () => {
|
|
useTopologyStore.getState().toggleTypeFilter('gateway');
|
|
expect(useTopologyStore.getState().typeFilters).toContain('gateway');
|
|
});
|
|
|
|
test('removes type when already present', () => {
|
|
useTopologyStore.getState().toggleTypeFilter('gateway');
|
|
useTopologyStore.getState().toggleTypeFilter('gateway');
|
|
expect(useTopologyStore.getState().typeFilters).not.toContain('gateway');
|
|
});
|
|
});
|
|
});
|