@principal-ai/principal-view-react 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/dist/components/ConfigurationSelector.d.ts +37 -0
- package/dist/components/ConfigurationSelector.d.ts.map +1 -0
- package/dist/components/ConfigurationSelector.js +67 -0
- package/dist/components/ConfigurationSelector.js.map +1 -0
- package/dist/components/EdgeInfoPanel.d.ts +16 -0
- package/dist/components/EdgeInfoPanel.d.ts.map +1 -0
- package/dist/components/EdgeInfoPanel.js +85 -0
- package/dist/components/EdgeInfoPanel.js.map +1 -0
- package/dist/components/EventLog.d.ts +20 -0
- package/dist/components/EventLog.d.ts.map +1 -0
- package/dist/components/EventLog.js +13 -0
- package/dist/components/EventLog.js.map +1 -0
- package/dist/components/EventLog.test.d.ts +2 -0
- package/dist/components/EventLog.test.d.ts.map +1 -0
- package/dist/components/EventLog.test.js +73 -0
- package/dist/components/EventLog.test.js.map +1 -0
- package/dist/components/GraphRenderer.d.ts +121 -0
- package/dist/components/GraphRenderer.d.ts.map +1 -0
- package/dist/components/GraphRenderer.js +809 -0
- package/dist/components/GraphRenderer.js.map +1 -0
- package/dist/components/GraphRenderer.test.d.ts +2 -0
- package/dist/components/GraphRenderer.test.d.ts.map +1 -0
- package/dist/components/GraphRenderer.test.js +88 -0
- package/dist/components/GraphRenderer.test.js.map +1 -0
- package/dist/components/MetricsDashboard.d.ts +14 -0
- package/dist/components/MetricsDashboard.d.ts.map +1 -0
- package/dist/components/MetricsDashboard.js +13 -0
- package/dist/components/MetricsDashboard.js.map +1 -0
- package/dist/components/NodeInfoPanel.d.ts +21 -0
- package/dist/components/NodeInfoPanel.d.ts.map +1 -0
- package/dist/components/NodeInfoPanel.js +217 -0
- package/dist/components/NodeInfoPanel.js.map +1 -0
- package/dist/edges/CustomEdge.d.ts +16 -0
- package/dist/edges/CustomEdge.d.ts.map +1 -0
- package/dist/edges/CustomEdge.js +200 -0
- package/dist/edges/CustomEdge.js.map +1 -0
- package/dist/edges/GenericEdge.d.ts +18 -0
- package/dist/edges/GenericEdge.d.ts.map +1 -0
- package/dist/edges/GenericEdge.js +14 -0
- package/dist/edges/GenericEdge.js.map +1 -0
- package/dist/hooks/usePathBasedEvents.d.ts +42 -0
- package/dist/hooks/usePathBasedEvents.d.ts.map +1 -0
- package/dist/hooks/usePathBasedEvents.js +122 -0
- package/dist/hooks/usePathBasedEvents.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/CustomNode.d.ts +18 -0
- package/dist/nodes/CustomNode.d.ts.map +1 -0
- package/dist/nodes/CustomNode.js +298 -0
- package/dist/nodes/CustomNode.js.map +1 -0
- package/dist/nodes/GenericNode.d.ts +20 -0
- package/dist/nodes/GenericNode.d.ts.map +1 -0
- package/dist/nodes/GenericNode.js +24 -0
- package/dist/nodes/GenericNode.js.map +1 -0
- package/dist/utils/animationMapping.d.ts +53 -0
- package/dist/utils/animationMapping.d.ts.map +1 -0
- package/dist/utils/animationMapping.js +133 -0
- package/dist/utils/animationMapping.js.map +1 -0
- package/dist/utils/graphConverter.d.ts +22 -0
- package/dist/utils/graphConverter.d.ts.map +1 -0
- package/dist/utils/graphConverter.js +176 -0
- package/dist/utils/graphConverter.js.map +1 -0
- package/dist/utils/iconResolver.d.ts +29 -0
- package/dist/utils/iconResolver.d.ts.map +1 -0
- package/dist/utils/iconResolver.js +68 -0
- package/dist/utils/iconResolver.js.map +1 -0
- package/package.json +61 -0
- package/src/components/ConfigurationSelector.tsx +147 -0
- package/src/components/EdgeInfoPanel.tsx +198 -0
- package/src/components/EventLog.test.tsx +85 -0
- package/src/components/EventLog.tsx +51 -0
- package/src/components/GraphRenderer.test.tsx +118 -0
- package/src/components/GraphRenderer.tsx +1222 -0
- package/src/components/MetricsDashboard.tsx +40 -0
- package/src/components/NodeInfoPanel.tsx +425 -0
- package/src/edges/CustomEdge.tsx +344 -0
- package/src/edges/GenericEdge.tsx +40 -0
- package/src/hooks/usePathBasedEvents.ts +182 -0
- package/src/index.ts +67 -0
- package/src/nodes/CustomNode.tsx +432 -0
- package/src/nodes/GenericNode.tsx +54 -0
- package/src/stories/AnimationWorkshop.stories.tsx +608 -0
- package/src/stories/EventDrivenAnimations.stories.tsx +499 -0
- package/src/stories/EventLog.stories.tsx +161 -0
- package/src/stories/GraphRenderer.stories.tsx +628 -0
- package/src/stories/Introduction.mdx +51 -0
- package/src/stories/MetricsDashboard.stories.tsx +227 -0
- package/src/stories/MultiConfig.stories.tsx +531 -0
- package/src/stories/MultiDirectionalConnections.stories.tsx +345 -0
- package/src/stories/NodeShapes.stories.tsx +769 -0
- package/src/utils/animationMapping.ts +170 -0
- package/src/utils/graphConverter.ts +218 -0
- package/src/utils/iconResolver.tsx +49 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { EdgeState, EdgeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
3
|
+
|
|
4
|
+
export interface EdgeInfoPanelProps {
|
|
5
|
+
edge: EdgeState;
|
|
6
|
+
typeDefinition: EdgeTypeDefinition;
|
|
7
|
+
sourceNodeId: string;
|
|
8
|
+
targetNodeId: string;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
/** Optional callback to delete the edge. If not provided, delete button is hidden. */
|
|
11
|
+
onDelete?: (edgeId: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Panel that displays information about a selected edge
|
|
16
|
+
*/
|
|
17
|
+
export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
|
|
18
|
+
edge,
|
|
19
|
+
typeDefinition,
|
|
20
|
+
sourceNodeId,
|
|
21
|
+
targetNodeId,
|
|
22
|
+
onClose,
|
|
23
|
+
onDelete,
|
|
24
|
+
}) => {
|
|
25
|
+
const color = typeDefinition.color || '#888';
|
|
26
|
+
|
|
27
|
+
// Get fields to display based on dataSchema
|
|
28
|
+
const displayFields = typeDefinition.dataSchema
|
|
29
|
+
? Object.entries(typeDefinition.dataSchema)
|
|
30
|
+
.filter(([, schema]) => schema.displayInInfo)
|
|
31
|
+
.map(([field, schema]) => ({
|
|
32
|
+
field,
|
|
33
|
+
label: schema.label || field,
|
|
34
|
+
value: edge.data?.[field],
|
|
35
|
+
}))
|
|
36
|
+
: [];
|
|
37
|
+
|
|
38
|
+
// Always show basic edge data if no schema is defined
|
|
39
|
+
const hasSchemaFields = displayFields.length > 0;
|
|
40
|
+
const edgeDataEntries = edge.data ? Object.entries(edge.data) : [];
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
style={{
|
|
45
|
+
position: 'absolute',
|
|
46
|
+
top: '60px',
|
|
47
|
+
right: '20px',
|
|
48
|
+
backgroundColor: 'white',
|
|
49
|
+
borderRadius: '8px',
|
|
50
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
51
|
+
padding: '16px',
|
|
52
|
+
minWidth: '250px',
|
|
53
|
+
maxWidth: '350px',
|
|
54
|
+
zIndex: 1000,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{/* Header */}
|
|
58
|
+
<div style={{
|
|
59
|
+
display: 'flex',
|
|
60
|
+
justifyContent: 'space-between',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
marginBottom: '12px',
|
|
63
|
+
paddingBottom: '8px',
|
|
64
|
+
borderBottom: `2px solid ${color}`,
|
|
65
|
+
}}>
|
|
66
|
+
<div style={{ fontWeight: 'bold', fontSize: '14px' }}>
|
|
67
|
+
Edge Information
|
|
68
|
+
</div>
|
|
69
|
+
<button
|
|
70
|
+
onClick={onClose}
|
|
71
|
+
style={{
|
|
72
|
+
border: 'none',
|
|
73
|
+
background: 'none',
|
|
74
|
+
cursor: 'pointer',
|
|
75
|
+
fontSize: '18px',
|
|
76
|
+
color: '#666',
|
|
77
|
+
padding: '0',
|
|
78
|
+
width: '24px',
|
|
79
|
+
height: '24px',
|
|
80
|
+
display: 'flex',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
justifyContent: 'center',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
×
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Edge Type */}
|
|
90
|
+
<div style={{ marginBottom: '12px' }}>
|
|
91
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
92
|
+
Type
|
|
93
|
+
</div>
|
|
94
|
+
<div style={{
|
|
95
|
+
fontSize: '12px',
|
|
96
|
+
padding: '4px 8px',
|
|
97
|
+
backgroundColor: color,
|
|
98
|
+
color: 'white',
|
|
99
|
+
borderRadius: '4px',
|
|
100
|
+
display: 'inline-block',
|
|
101
|
+
}}>
|
|
102
|
+
{edge.type}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Connection Info */}
|
|
107
|
+
<div style={{ marginBottom: '12px' }}>
|
|
108
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
109
|
+
Connection
|
|
110
|
+
</div>
|
|
111
|
+
<div style={{ fontSize: '12px', color: '#333' }}>
|
|
112
|
+
<span style={{ fontFamily: 'monospace', backgroundColor: '#f0f0f0', padding: '2px 6px', borderRadius: '3px' }}>
|
|
113
|
+
{sourceNodeId}
|
|
114
|
+
</span>
|
|
115
|
+
<span style={{ margin: '0 8px', color: '#888' }}>→</span>
|
|
116
|
+
<span style={{ fontFamily: 'monospace', backgroundColor: '#f0f0f0', padding: '2px 6px', borderRadius: '3px' }}>
|
|
117
|
+
{targetNodeId}
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Display schema-defined fields */}
|
|
123
|
+
{hasSchemaFields && (
|
|
124
|
+
<div style={{ marginBottom: '12px' }}>
|
|
125
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
126
|
+
Properties
|
|
127
|
+
</div>
|
|
128
|
+
{displayFields.map(({ field, label, value }) => (
|
|
129
|
+
<div key={field} style={{ marginBottom: '8px' }}>
|
|
130
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
|
|
131
|
+
{label}
|
|
132
|
+
</div>
|
|
133
|
+
<div style={{ fontSize: '12px', color: '#333' }}>
|
|
134
|
+
{value !== undefined && value !== null
|
|
135
|
+
? typeof value === 'object'
|
|
136
|
+
? JSON.stringify(value, null, 2)
|
|
137
|
+
: String(value)
|
|
138
|
+
: '-'}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{/* Show all edge data if no schema is defined */}
|
|
146
|
+
{!hasSchemaFields && edgeDataEntries.length > 0 && (
|
|
147
|
+
<div style={{ marginBottom: '12px' }}>
|
|
148
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
149
|
+
Data
|
|
150
|
+
</div>
|
|
151
|
+
{edgeDataEntries.map(([key, value]) => (
|
|
152
|
+
<div key={key} style={{ marginBottom: '8px' }}>
|
|
153
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
|
|
154
|
+
{key}
|
|
155
|
+
</div>
|
|
156
|
+
<div style={{ fontSize: '12px', color: '#333', wordBreak: 'break-word' }}>
|
|
157
|
+
{value !== undefined && value !== null
|
|
158
|
+
? typeof value === 'object'
|
|
159
|
+
? JSON.stringify(value, null, 2)
|
|
160
|
+
: String(value)
|
|
161
|
+
: '-'}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{/* Metadata */}
|
|
169
|
+
<div style={{ fontSize: '10px', color: '#999', marginTop: '12px', paddingTop: '8px', borderTop: '1px solid #eee' }}>
|
|
170
|
+
ID: {edge.id}
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{/* Delete Button */}
|
|
174
|
+
{onDelete && (
|
|
175
|
+
<button
|
|
176
|
+
onClick={() => {
|
|
177
|
+
onDelete(edge.id);
|
|
178
|
+
onClose();
|
|
179
|
+
}}
|
|
180
|
+
style={{
|
|
181
|
+
marginTop: '12px',
|
|
182
|
+
width: '100%',
|
|
183
|
+
padding: '8px 12px',
|
|
184
|
+
backgroundColor: '#dc3545',
|
|
185
|
+
color: 'white',
|
|
186
|
+
border: 'none',
|
|
187
|
+
borderRadius: '4px',
|
|
188
|
+
cursor: 'pointer',
|
|
189
|
+
fontSize: '12px',
|
|
190
|
+
fontWeight: 'bold',
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
Delete Edge
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { EventLog } from './EventLog';
|
|
4
|
+
import type { GraphEvent, Violation } from '@principal-ai/principal-view-core';
|
|
5
|
+
|
|
6
|
+
describe('EventLog', () => {
|
|
7
|
+
const testEvents: GraphEvent[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 'evt-1',
|
|
10
|
+
type: 'node_created',
|
|
11
|
+
timestamp: Date.now(),
|
|
12
|
+
category: 'node',
|
|
13
|
+
operation: 'create',
|
|
14
|
+
payload: {
|
|
15
|
+
operation: 'create',
|
|
16
|
+
nodeId: 'user-1',
|
|
17
|
+
nodeType: 'user',
|
|
18
|
+
data: { userId: 'alice' },
|
|
19
|
+
},
|
|
20
|
+
expected: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'evt-2',
|
|
24
|
+
type: 'edge_created',
|
|
25
|
+
timestamp: Date.now(),
|
|
26
|
+
category: 'edge',
|
|
27
|
+
operation: 'create',
|
|
28
|
+
payload: {
|
|
29
|
+
operation: 'create',
|
|
30
|
+
edgeId: 'conn-1',
|
|
31
|
+
edgeType: 'connection',
|
|
32
|
+
from: 'user-1',
|
|
33
|
+
to: 'user-2',
|
|
34
|
+
},
|
|
35
|
+
expected: true,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const testViolations: Violation[] = [
|
|
40
|
+
{
|
|
41
|
+
id: 'violation-1',
|
|
42
|
+
severity: 'error',
|
|
43
|
+
type: 'connection',
|
|
44
|
+
description: 'Invalid connection',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
it('should render without crashing', () => {
|
|
49
|
+
render(<EventLog events={testEvents} />);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText(/Event Log/i)).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should display event count', () => {
|
|
55
|
+
render(<EventLog events={testEvents} />);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText(/Events: 2/i)).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should display violation count', () => {
|
|
61
|
+
render(<EventLog events={testEvents} violations={testViolations} />);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByText(/Violations: 1/i)).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should render with empty events', () => {
|
|
67
|
+
render(<EventLog events={[]} />);
|
|
68
|
+
|
|
69
|
+
expect(screen.getByText(/Events: 0/i)).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should apply custom className', () => {
|
|
73
|
+
const { container } = render(<EventLog events={testEvents} className="custom-log" />);
|
|
74
|
+
|
|
75
|
+
const element = container.querySelector('.custom-log');
|
|
76
|
+
expect(element).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should apply custom maxHeight', () => {
|
|
80
|
+
const { container } = render(<EventLog events={testEvents} maxHeight="300px" />);
|
|
81
|
+
|
|
82
|
+
const element = container.querySelector('div');
|
|
83
|
+
expect(element?.style.maxHeight).toBe('300px');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { GraphEvent, Violation } from '@principal-ai/principal-view-core';
|
|
3
|
+
|
|
4
|
+
export interface EventLogProps {
|
|
5
|
+
/** List of events to display */
|
|
6
|
+
events: GraphEvent[];
|
|
7
|
+
|
|
8
|
+
/** Optional violations to highlight */
|
|
9
|
+
violations?: Violation[];
|
|
10
|
+
|
|
11
|
+
/** Callback when an event is clicked */
|
|
12
|
+
onEventClick?: (event: GraphEvent) => void;
|
|
13
|
+
|
|
14
|
+
/** Optional class name */
|
|
15
|
+
className?: string;
|
|
16
|
+
|
|
17
|
+
/** Optional max height */
|
|
18
|
+
maxHeight?: number | string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Event log component for displaying graph events
|
|
23
|
+
* TODO: Implement filtering, search, and severity indicators
|
|
24
|
+
*/
|
|
25
|
+
export const EventLog: React.FC<EventLogProps> = ({
|
|
26
|
+
events,
|
|
27
|
+
violations = [],
|
|
28
|
+
className,
|
|
29
|
+
maxHeight = '400px',
|
|
30
|
+
}) => {
|
|
31
|
+
return (
|
|
32
|
+
<div className={className} style={{ maxHeight, overflowY: 'auto', border: '1px solid #ccc' }}>
|
|
33
|
+
<div style={{ padding: '10px' }}>
|
|
34
|
+
<h3>Event Log (TODO)</h3>
|
|
35
|
+
<p>Events: {events.length}</p>
|
|
36
|
+
<p>Violations: {violations.length}</p>
|
|
37
|
+
<div>
|
|
38
|
+
<strong>TODO:</strong>
|
|
39
|
+
<ul>
|
|
40
|
+
<li>Display events in chronological order</li>
|
|
41
|
+
<li>Show event type, category, operation</li>
|
|
42
|
+
<li>Highlight violations with color coding</li>
|
|
43
|
+
<li>Add filtering by category, type, severity</li>
|
|
44
|
+
<li>Add text search</li>
|
|
45
|
+
<li>Add timestamp formatting</li>
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { GraphRenderer } from './GraphRenderer';
|
|
4
|
+
import type { GraphConfiguration, NodeState, EdgeState } from '@principal-ai/principal-view-core';
|
|
5
|
+
|
|
6
|
+
describe('GraphRenderer', () => {
|
|
7
|
+
const testConfig: GraphConfiguration = {
|
|
8
|
+
metadata: {
|
|
9
|
+
name: 'Test Graph',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
},
|
|
12
|
+
nodeTypes: {
|
|
13
|
+
user: {
|
|
14
|
+
shape: 'circle',
|
|
15
|
+
color: '#4CAF50',
|
|
16
|
+
dataSchema: {
|
|
17
|
+
userId: { type: 'string', required: true },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
edgeTypes: {
|
|
22
|
+
connection: {
|
|
23
|
+
style: 'solid',
|
|
24
|
+
directed: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
allowedConnections: [{ from: 'user', to: 'user', via: 'connection' }],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const testNodes: NodeState[] = [
|
|
31
|
+
{
|
|
32
|
+
id: 'user-1',
|
|
33
|
+
type: 'user',
|
|
34
|
+
data: { userId: 'alice' },
|
|
35
|
+
createdAt: Date.now(),
|
|
36
|
+
updatedAt: Date.now(),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'user-2',
|
|
40
|
+
type: 'user',
|
|
41
|
+
data: { userId: 'bob' },
|
|
42
|
+
createdAt: Date.now(),
|
|
43
|
+
updatedAt: Date.now(),
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const testEdges: EdgeState[] = [
|
|
48
|
+
{
|
|
49
|
+
id: 'conn-1',
|
|
50
|
+
type: 'connection',
|
|
51
|
+
from: 'user-1',
|
|
52
|
+
to: 'user-2',
|
|
53
|
+
createdAt: Date.now(),
|
|
54
|
+
updatedAt: Date.now(),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
it('should render without crashing', () => {
|
|
59
|
+
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
60
|
+
|
|
61
|
+
expect(screen.getByText(/Graph Renderer/i)).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should display configuration name', () => {
|
|
65
|
+
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText(/Test Graph/i)).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should display node count', () => {
|
|
71
|
+
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText(/Nodes: 2/i)).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should display edge count', () => {
|
|
77
|
+
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
78
|
+
|
|
79
|
+
expect(screen.getByText(/Edges: 1/i)).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should render with empty nodes and edges', () => {
|
|
83
|
+
render(<GraphRenderer configuration={testConfig} nodes={[]} edges={[]} />);
|
|
84
|
+
|
|
85
|
+
expect(screen.getByText(/Nodes: 0/i)).toBeDefined();
|
|
86
|
+
expect(screen.getByText(/Edges: 0/i)).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should apply custom className', () => {
|
|
90
|
+
const { container } = render(
|
|
91
|
+
<GraphRenderer
|
|
92
|
+
configuration={testConfig}
|
|
93
|
+
nodes={testNodes}
|
|
94
|
+
edges={testEdges}
|
|
95
|
+
className="custom-class"
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const element = container.querySelector('.custom-class');
|
|
100
|
+
expect(element).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should apply custom width and height', () => {
|
|
104
|
+
const { container } = render(
|
|
105
|
+
<GraphRenderer
|
|
106
|
+
configuration={testConfig}
|
|
107
|
+
nodes={testNodes}
|
|
108
|
+
edges={testEdges}
|
|
109
|
+
width="500px"
|
|
110
|
+
height="400px"
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const element = container.querySelector('div');
|
|
115
|
+
expect(element?.style.width).toBe('500px');
|
|
116
|
+
expect(element?.style.height).toBe('400px');
|
|
117
|
+
});
|
|
118
|
+
});
|