flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b52__py3-none-any.whl
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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/dashboard/launcher.py +1 -1
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/helper/cli_helper.py +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/RECORD +117 -7
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for MessageHistoryTab and RunStatusTab components.
|
|
3
|
+
*
|
|
4
|
+
* Tests verify message history display, run status metrics, tab switching,
|
|
5
|
+
* and default tab preference handling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
9
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
10
|
+
import { useUIStore } from '../../store/uiStore';
|
|
11
|
+
|
|
12
|
+
// Message data types
|
|
13
|
+
interface MessageHistoryEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
type: string;
|
|
16
|
+
direction: 'consumed' | 'published';
|
|
17
|
+
payload: any;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
correlationId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Run status data types
|
|
23
|
+
interface RunStatusEntry {
|
|
24
|
+
runId: string;
|
|
25
|
+
startTime: number;
|
|
26
|
+
endTime: number;
|
|
27
|
+
duration: number;
|
|
28
|
+
status: 'completed' | 'error' | 'running';
|
|
29
|
+
metrics: {
|
|
30
|
+
tokensUsed?: number;
|
|
31
|
+
costUsd?: number;
|
|
32
|
+
artifactsProduced?: number;
|
|
33
|
+
};
|
|
34
|
+
errorMessage?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Mock MessageHistoryTab component
|
|
38
|
+
const MockMessageHistoryTab = ({
|
|
39
|
+
nodeId,
|
|
40
|
+
messages,
|
|
41
|
+
}: {
|
|
42
|
+
nodeId: string;
|
|
43
|
+
messages: MessageHistoryEntry[];
|
|
44
|
+
}) => {
|
|
45
|
+
const formatTimestamp = (timestamp: number) => {
|
|
46
|
+
return new Date(timestamp).toLocaleString();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const formatPayload = (payload: any) => {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(payload, null, 2);
|
|
52
|
+
} catch {
|
|
53
|
+
return String(payload);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div data-testid={`message-history-${nodeId}`}>
|
|
59
|
+
{messages.length === 0 ? (
|
|
60
|
+
<div data-testid="empty-messages">No messages yet</div>
|
|
61
|
+
) : (
|
|
62
|
+
<table data-testid="message-table">
|
|
63
|
+
<thead>
|
|
64
|
+
<tr>
|
|
65
|
+
<th>Time</th>
|
|
66
|
+
<th>Direction</th>
|
|
67
|
+
<th>Type</th>
|
|
68
|
+
<th>Correlation ID</th>
|
|
69
|
+
<th>Payload</th>
|
|
70
|
+
</tr>
|
|
71
|
+
</thead>
|
|
72
|
+
<tbody>
|
|
73
|
+
{messages.map((msg) => (
|
|
74
|
+
<tr key={msg.id} data-testid={`message-row-${msg.id}`}>
|
|
75
|
+
<td data-testid={`msg-time-${msg.id}`}>{formatTimestamp(msg.timestamp)}</td>
|
|
76
|
+
<td
|
|
77
|
+
data-testid={`msg-direction-${msg.id}`}
|
|
78
|
+
style={{
|
|
79
|
+
color: msg.direction === 'consumed' ? '#8be9fd' : '#50fa7b',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{msg.direction === 'consumed' ? '↓ Consumed' : '↑ Published'}
|
|
83
|
+
</td>
|
|
84
|
+
<td data-testid={`msg-type-${msg.id}`}>{msg.type}</td>
|
|
85
|
+
<td data-testid={`msg-correlation-${msg.id}`}>{msg.correlationId}</td>
|
|
86
|
+
<td data-testid={`msg-payload-${msg.id}`}>
|
|
87
|
+
<pre style={{ fontSize: '10px', maxHeight: '100px', overflow: 'auto' }}>
|
|
88
|
+
{formatPayload(msg.payload)}
|
|
89
|
+
</pre>
|
|
90
|
+
</td>
|
|
91
|
+
</tr>
|
|
92
|
+
))}
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Mock RunStatusTab component
|
|
101
|
+
const MockRunStatusTab = ({
|
|
102
|
+
nodeId,
|
|
103
|
+
runs,
|
|
104
|
+
}: {
|
|
105
|
+
nodeId: string;
|
|
106
|
+
runs: RunStatusEntry[];
|
|
107
|
+
}) => {
|
|
108
|
+
const formatDuration = (ms: number) => {
|
|
109
|
+
if (ms < 1000) return `${ms}ms`;
|
|
110
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const formatTimestamp = (timestamp: number) => {
|
|
114
|
+
return new Date(timestamp).toLocaleString();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div data-testid={`run-status-${nodeId}`}>
|
|
119
|
+
{runs.length === 0 ? (
|
|
120
|
+
<div data-testid="empty-runs">No runs yet</div>
|
|
121
|
+
) : (
|
|
122
|
+
<table data-testid="run-table">
|
|
123
|
+
<thead>
|
|
124
|
+
<tr>
|
|
125
|
+
<th>Run ID</th>
|
|
126
|
+
<th>Start Time</th>
|
|
127
|
+
<th>Duration</th>
|
|
128
|
+
<th>Status</th>
|
|
129
|
+
<th>Tokens</th>
|
|
130
|
+
<th>Cost</th>
|
|
131
|
+
<th>Artifacts</th>
|
|
132
|
+
</tr>
|
|
133
|
+
</thead>
|
|
134
|
+
<tbody>
|
|
135
|
+
{runs.map((run) => (
|
|
136
|
+
<tr key={run.runId} data-testid={`run-row-${run.runId}`}>
|
|
137
|
+
<td data-testid={`run-id-${run.runId}`}>{run.runId}</td>
|
|
138
|
+
<td data-testid={`run-start-${run.runId}`}>
|
|
139
|
+
{formatTimestamp(run.startTime)}
|
|
140
|
+
</td>
|
|
141
|
+
<td data-testid={`run-duration-${run.runId}`}>
|
|
142
|
+
{formatDuration(run.duration)}
|
|
143
|
+
</td>
|
|
144
|
+
<td
|
|
145
|
+
data-testid={`run-status-${run.runId}`}
|
|
146
|
+
style={{
|
|
147
|
+
color:
|
|
148
|
+
run.status === 'completed'
|
|
149
|
+
? '#50fa7b'
|
|
150
|
+
: run.status === 'error'
|
|
151
|
+
? '#ff5555'
|
|
152
|
+
: '#f1fa8c',
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
{run.status}
|
|
156
|
+
</td>
|
|
157
|
+
<td data-testid={`run-tokens-${run.runId}`}>
|
|
158
|
+
{run.metrics.tokensUsed ?? '-'}
|
|
159
|
+
</td>
|
|
160
|
+
<td data-testid={`run-cost-${run.runId}`}>
|
|
161
|
+
{run.metrics.costUsd ? `$${run.metrics.costUsd.toFixed(4)}` : '-'}
|
|
162
|
+
</td>
|
|
163
|
+
<td data-testid={`run-artifacts-${run.runId}`}>
|
|
164
|
+
{run.metrics.artifactsProduced ?? '-'}
|
|
165
|
+
</td>
|
|
166
|
+
</tr>
|
|
167
|
+
))}
|
|
168
|
+
</tbody>
|
|
169
|
+
</table>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Mock tab container component
|
|
176
|
+
const MockTabContainer = ({
|
|
177
|
+
nodeId,
|
|
178
|
+
messages,
|
|
179
|
+
runs,
|
|
180
|
+
}: {
|
|
181
|
+
nodeId: string;
|
|
182
|
+
messages: MessageHistoryEntry[];
|
|
183
|
+
runs: RunStatusEntry[];
|
|
184
|
+
}) => {
|
|
185
|
+
const window = useUIStore((state) => state.detailWindows.get(nodeId));
|
|
186
|
+
const updateDetailWindow = useUIStore((state) => state.updateDetailWindow);
|
|
187
|
+
|
|
188
|
+
if (!window) return null;
|
|
189
|
+
|
|
190
|
+
const activeTab = window.activeTab;
|
|
191
|
+
|
|
192
|
+
const handleTabClick = (tab: 'liveOutput' | 'messageHistory' | 'runStatus') => {
|
|
193
|
+
updateDetailWindow(nodeId, { activeTab: tab });
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div data-testid={`tab-container-${nodeId}`}>
|
|
198
|
+
<div data-testid="tab-buttons">
|
|
199
|
+
<button
|
|
200
|
+
data-testid="tab-live-output"
|
|
201
|
+
onClick={() => handleTabClick('liveOutput')}
|
|
202
|
+
data-active={activeTab === 'liveOutput'}
|
|
203
|
+
style={{
|
|
204
|
+
fontWeight: activeTab === 'liveOutput' ? 'bold' : 'normal',
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
Live Output
|
|
208
|
+
</button>
|
|
209
|
+
<button
|
|
210
|
+
data-testid="tab-message-history"
|
|
211
|
+
onClick={() => handleTabClick('messageHistory')}
|
|
212
|
+
data-active={activeTab === 'messageHistory'}
|
|
213
|
+
style={{
|
|
214
|
+
fontWeight: activeTab === 'messageHistory' ? 'bold' : 'normal',
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
Message History
|
|
218
|
+
</button>
|
|
219
|
+
<button
|
|
220
|
+
data-testid="tab-run-status"
|
|
221
|
+
onClick={() => handleTabClick('runStatus')}
|
|
222
|
+
data-active={activeTab === 'runStatus'}
|
|
223
|
+
style={{
|
|
224
|
+
fontWeight: activeTab === 'runStatus' ? 'bold' : 'normal',
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
Run Status
|
|
228
|
+
</button>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div data-testid="tab-content">
|
|
232
|
+
{activeTab === 'liveOutput' && (
|
|
233
|
+
<div data-testid="live-output-content">Live Output Tab</div>
|
|
234
|
+
)}
|
|
235
|
+
{activeTab === 'messageHistory' && (
|
|
236
|
+
<MockMessageHistoryTab nodeId={nodeId} messages={messages} />
|
|
237
|
+
)}
|
|
238
|
+
{activeTab === 'runStatus' && (
|
|
239
|
+
<MockRunStatusTab nodeId={nodeId} runs={runs} />
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
describe('MessageHistoryTab', () => {
|
|
247
|
+
beforeEach(() => {
|
|
248
|
+
useUIStore.setState({
|
|
249
|
+
mode: 'agent',
|
|
250
|
+
selectedNodeIds: new Set(),
|
|
251
|
+
detailWindows: new Map(),
|
|
252
|
+
defaultTab: 'liveOutput',
|
|
253
|
+
layoutDirection: 'TB',
|
|
254
|
+
autoLayoutEnabled: true,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('Display', () => {
|
|
259
|
+
it('should render empty state when no messages', () => {
|
|
260
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={[]} />);
|
|
261
|
+
|
|
262
|
+
expect(screen.getByTestId('empty-messages')).toBeInTheDocument();
|
|
263
|
+
expect(screen.getByText('No messages yet')).toBeInTheDocument();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should display messages in table format', () => {
|
|
267
|
+
const messages: MessageHistoryEntry[] = [
|
|
268
|
+
{
|
|
269
|
+
id: 'msg-1',
|
|
270
|
+
type: 'Movie',
|
|
271
|
+
direction: 'consumed',
|
|
272
|
+
payload: { title: 'Inception' },
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
correlationId: 'corr-1',
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
279
|
+
|
|
280
|
+
expect(screen.getByTestId('message-table')).toBeInTheDocument();
|
|
281
|
+
expect(screen.getByTestId('message-row-msg-1')).toBeInTheDocument();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should display consumed messages with correct styling', () => {
|
|
285
|
+
const messages: MessageHistoryEntry[] = [
|
|
286
|
+
{
|
|
287
|
+
id: 'msg-1',
|
|
288
|
+
type: 'Input',
|
|
289
|
+
direction: 'consumed',
|
|
290
|
+
payload: { data: 'test' },
|
|
291
|
+
timestamp: Date.now(),
|
|
292
|
+
correlationId: 'corr-1',
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
297
|
+
|
|
298
|
+
const direction = screen.getByTestId('msg-direction-msg-1');
|
|
299
|
+
expect(direction).toHaveTextContent('↓ Consumed');
|
|
300
|
+
expect(direction).toHaveStyle({ color: '#8be9fd' });
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should display published messages with correct styling', () => {
|
|
304
|
+
const messages: MessageHistoryEntry[] = [
|
|
305
|
+
{
|
|
306
|
+
id: 'msg-1',
|
|
307
|
+
type: 'Output',
|
|
308
|
+
direction: 'published',
|
|
309
|
+
payload: { result: 'success' },
|
|
310
|
+
timestamp: Date.now(),
|
|
311
|
+
correlationId: 'corr-1',
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
316
|
+
|
|
317
|
+
const direction = screen.getByTestId('msg-direction-msg-1');
|
|
318
|
+
expect(direction).toHaveTextContent('↑ Published');
|
|
319
|
+
expect(direction).toHaveStyle({ color: '#50fa7b' });
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should display message type', () => {
|
|
323
|
+
const messages: MessageHistoryEntry[] = [
|
|
324
|
+
{
|
|
325
|
+
id: 'msg-1',
|
|
326
|
+
type: 'Movie',
|
|
327
|
+
direction: 'consumed',
|
|
328
|
+
payload: {},
|
|
329
|
+
timestamp: Date.now(),
|
|
330
|
+
correlationId: 'corr-1',
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
335
|
+
|
|
336
|
+
expect(screen.getByTestId('msg-type-msg-1')).toHaveTextContent('Movie');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should display correlation ID', () => {
|
|
340
|
+
const messages: MessageHistoryEntry[] = [
|
|
341
|
+
{
|
|
342
|
+
id: 'msg-1',
|
|
343
|
+
type: 'Test',
|
|
344
|
+
direction: 'consumed',
|
|
345
|
+
payload: {},
|
|
346
|
+
timestamp: Date.now(),
|
|
347
|
+
correlationId: 'corr-123-abc',
|
|
348
|
+
},
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
352
|
+
|
|
353
|
+
expect(screen.getByTestId('msg-correlation-msg-1')).toHaveTextContent('corr-123-abc');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should format and display payload as JSON', () => {
|
|
357
|
+
const messages: MessageHistoryEntry[] = [
|
|
358
|
+
{
|
|
359
|
+
id: 'msg-1',
|
|
360
|
+
type: 'Movie',
|
|
361
|
+
direction: 'consumed',
|
|
362
|
+
payload: { title: 'Inception', year: 2010 },
|
|
363
|
+
timestamp: Date.now(),
|
|
364
|
+
correlationId: 'corr-1',
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
369
|
+
|
|
370
|
+
const payload = screen.getByTestId('msg-payload-msg-1');
|
|
371
|
+
expect(payload.textContent).toContain('title');
|
|
372
|
+
expect(payload.textContent).toContain('Inception');
|
|
373
|
+
expect(payload.textContent).toContain('2010');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should display timestamp in readable format', () => {
|
|
377
|
+
const timestamp = Date.now();
|
|
378
|
+
const messages: MessageHistoryEntry[] = [
|
|
379
|
+
{
|
|
380
|
+
id: 'msg-1',
|
|
381
|
+
type: 'Test',
|
|
382
|
+
direction: 'consumed',
|
|
383
|
+
payload: {},
|
|
384
|
+
timestamp,
|
|
385
|
+
correlationId: 'corr-1',
|
|
386
|
+
},
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
390
|
+
|
|
391
|
+
const timeElement = screen.getByTestId('msg-time-msg-1');
|
|
392
|
+
expect(timeElement.textContent).toBeTruthy();
|
|
393
|
+
expect(timeElement.textContent).not.toBe(String(timestamp));
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('Multiple Messages', () => {
|
|
398
|
+
it('should display multiple messages in order', () => {
|
|
399
|
+
const messages: MessageHistoryEntry[] = [
|
|
400
|
+
{
|
|
401
|
+
id: 'msg-1',
|
|
402
|
+
type: 'Input',
|
|
403
|
+
direction: 'consumed',
|
|
404
|
+
payload: { data: 1 },
|
|
405
|
+
timestamp: Date.now(),
|
|
406
|
+
correlationId: 'corr-1',
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: 'msg-2',
|
|
410
|
+
type: 'Output',
|
|
411
|
+
direction: 'published',
|
|
412
|
+
payload: { result: 1 },
|
|
413
|
+
timestamp: Date.now() + 1000,
|
|
414
|
+
correlationId: 'corr-2',
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: 'msg-3',
|
|
418
|
+
type: 'Input',
|
|
419
|
+
direction: 'consumed',
|
|
420
|
+
payload: { data: 2 },
|
|
421
|
+
timestamp: Date.now() + 2000,
|
|
422
|
+
correlationId: 'corr-3',
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
427
|
+
|
|
428
|
+
expect(screen.getByTestId('message-row-msg-1')).toBeInTheDocument();
|
|
429
|
+
expect(screen.getByTestId('message-row-msg-2')).toBeInTheDocument();
|
|
430
|
+
expect(screen.getByTestId('message-row-msg-3')).toBeInTheDocument();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should handle mixed consumed and published messages', () => {
|
|
434
|
+
const messages: MessageHistoryEntry[] = [
|
|
435
|
+
{
|
|
436
|
+
id: 'msg-1',
|
|
437
|
+
type: 'Movie',
|
|
438
|
+
direction: 'consumed',
|
|
439
|
+
payload: {},
|
|
440
|
+
timestamp: Date.now(),
|
|
441
|
+
correlationId: 'corr-1',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: 'msg-2',
|
|
445
|
+
type: 'Tagline',
|
|
446
|
+
direction: 'published',
|
|
447
|
+
payload: {},
|
|
448
|
+
timestamp: Date.now() + 1000,
|
|
449
|
+
correlationId: 'corr-2',
|
|
450
|
+
},
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
454
|
+
|
|
455
|
+
expect(screen.getByTestId('msg-direction-msg-1')).toHaveTextContent('↓ Consumed');
|
|
456
|
+
expect(screen.getByTestId('msg-direction-msg-2')).toHaveTextContent('↑ Published');
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('Edge Cases', () => {
|
|
461
|
+
it('should handle complex nested payload', () => {
|
|
462
|
+
const messages: MessageHistoryEntry[] = [
|
|
463
|
+
{
|
|
464
|
+
id: 'msg-1',
|
|
465
|
+
type: 'Complex',
|
|
466
|
+
direction: 'consumed',
|
|
467
|
+
payload: {
|
|
468
|
+
nested: {
|
|
469
|
+
deeply: {
|
|
470
|
+
value: 'test',
|
|
471
|
+
array: [1, 2, 3],
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
timestamp: Date.now(),
|
|
476
|
+
correlationId: 'corr-1',
|
|
477
|
+
},
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
481
|
+
|
|
482
|
+
const payload = screen.getByTestId('msg-payload-msg-1');
|
|
483
|
+
expect(payload.textContent).toContain('nested');
|
|
484
|
+
expect(payload.textContent).toContain('deeply');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should handle null payload', () => {
|
|
488
|
+
const messages: MessageHistoryEntry[] = [
|
|
489
|
+
{
|
|
490
|
+
id: 'msg-1',
|
|
491
|
+
type: 'Test',
|
|
492
|
+
direction: 'consumed',
|
|
493
|
+
payload: null,
|
|
494
|
+
timestamp: Date.now(),
|
|
495
|
+
correlationId: 'corr-1',
|
|
496
|
+
},
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
expect(() => {
|
|
500
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
501
|
+
}).not.toThrow();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should handle large payloads with scrolling', () => {
|
|
505
|
+
const largePayload = {
|
|
506
|
+
data: Array.from({ length: 100 }, (_, i) => ({ id: i, value: `Item ${i}` })),
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const messages: MessageHistoryEntry[] = [
|
|
510
|
+
{
|
|
511
|
+
id: 'msg-1',
|
|
512
|
+
type: 'Large',
|
|
513
|
+
direction: 'consumed',
|
|
514
|
+
payload: largePayload,
|
|
515
|
+
timestamp: Date.now(),
|
|
516
|
+
correlationId: 'corr-1',
|
|
517
|
+
},
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
render(<MockMessageHistoryTab nodeId="agent-1" messages={messages} />);
|
|
521
|
+
|
|
522
|
+
const payload = screen.getByTestId('msg-payload-msg-1');
|
|
523
|
+
const pre = payload.querySelector('pre');
|
|
524
|
+
expect(pre).toHaveStyle({ maxHeight: '100px', overflow: 'auto' });
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe('RunStatusTab', () => {
|
|
530
|
+
beforeEach(() => {
|
|
531
|
+
useUIStore.setState({
|
|
532
|
+
mode: 'agent',
|
|
533
|
+
selectedNodeIds: new Set(),
|
|
534
|
+
detailWindows: new Map(),
|
|
535
|
+
defaultTab: 'liveOutput',
|
|
536
|
+
layoutDirection: 'TB',
|
|
537
|
+
autoLayoutEnabled: true,
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('Display', () => {
|
|
542
|
+
it('should render empty state when no runs', () => {
|
|
543
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={[]} />);
|
|
544
|
+
|
|
545
|
+
expect(screen.getByTestId('empty-runs')).toBeInTheDocument();
|
|
546
|
+
expect(screen.getByText('No runs yet')).toBeInTheDocument();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should display runs in table format', () => {
|
|
550
|
+
const runs: RunStatusEntry[] = [
|
|
551
|
+
{
|
|
552
|
+
runId: 'run-1',
|
|
553
|
+
startTime: Date.now(),
|
|
554
|
+
endTime: Date.now() + 1000,
|
|
555
|
+
duration: 1000,
|
|
556
|
+
status: 'completed',
|
|
557
|
+
metrics: { tokensUsed: 100, costUsd: 0.01 },
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
|
|
561
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
562
|
+
|
|
563
|
+
expect(screen.getByTestId('run-table')).toBeInTheDocument();
|
|
564
|
+
expect(screen.getByTestId('run-row-run-1')).toBeInTheDocument();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('should display run ID', () => {
|
|
568
|
+
const runs: RunStatusEntry[] = [
|
|
569
|
+
{
|
|
570
|
+
runId: 'run-abc-123',
|
|
571
|
+
startTime: Date.now(),
|
|
572
|
+
endTime: Date.now() + 1000,
|
|
573
|
+
duration: 1000,
|
|
574
|
+
status: 'completed',
|
|
575
|
+
metrics: {},
|
|
576
|
+
},
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
580
|
+
|
|
581
|
+
expect(screen.getByTestId('run-id-run-abc-123')).toHaveTextContent('run-abc-123');
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should display start time in readable format', () => {
|
|
585
|
+
const startTime = Date.now();
|
|
586
|
+
const runs: RunStatusEntry[] = [
|
|
587
|
+
{
|
|
588
|
+
runId: 'run-1',
|
|
589
|
+
startTime,
|
|
590
|
+
endTime: startTime + 1000,
|
|
591
|
+
duration: 1000,
|
|
592
|
+
status: 'completed',
|
|
593
|
+
metrics: {},
|
|
594
|
+
},
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
598
|
+
|
|
599
|
+
const startElement = screen.getByTestId('run-start-run-1');
|
|
600
|
+
expect(startElement.textContent).toBeTruthy();
|
|
601
|
+
expect(startElement.textContent).not.toBe(String(startTime));
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should format duration in milliseconds', () => {
|
|
605
|
+
const runs: RunStatusEntry[] = [
|
|
606
|
+
{
|
|
607
|
+
runId: 'run-1',
|
|
608
|
+
startTime: Date.now(),
|
|
609
|
+
endTime: Date.now() + 500,
|
|
610
|
+
duration: 500,
|
|
611
|
+
status: 'completed',
|
|
612
|
+
metrics: {},
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
|
|
616
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
617
|
+
|
|
618
|
+
expect(screen.getByTestId('run-duration-run-1')).toHaveTextContent('500ms');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('should format duration in seconds', () => {
|
|
622
|
+
const runs: RunStatusEntry[] = [
|
|
623
|
+
{
|
|
624
|
+
runId: 'run-1',
|
|
625
|
+
startTime: Date.now(),
|
|
626
|
+
endTime: Date.now() + 2500,
|
|
627
|
+
duration: 2500,
|
|
628
|
+
status: 'completed',
|
|
629
|
+
metrics: {},
|
|
630
|
+
},
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
634
|
+
|
|
635
|
+
expect(screen.getByTestId('run-duration-run-1')).toHaveTextContent('2.50s');
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('should display completed status with green color', () => {
|
|
639
|
+
const runs: RunStatusEntry[] = [
|
|
640
|
+
{
|
|
641
|
+
runId: 'run-1',
|
|
642
|
+
startTime: Date.now(),
|
|
643
|
+
endTime: Date.now() + 1000,
|
|
644
|
+
duration: 1000,
|
|
645
|
+
status: 'completed',
|
|
646
|
+
metrics: {},
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
651
|
+
|
|
652
|
+
const status = screen.getByTestId('run-status-run-1');
|
|
653
|
+
expect(status).toHaveTextContent('completed');
|
|
654
|
+
expect(status).toHaveStyle({ color: '#50fa7b' });
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('should display error status with red color', () => {
|
|
658
|
+
const runs: RunStatusEntry[] = [
|
|
659
|
+
{
|
|
660
|
+
runId: 'run-1',
|
|
661
|
+
startTime: Date.now(),
|
|
662
|
+
endTime: Date.now() + 1000,
|
|
663
|
+
duration: 1000,
|
|
664
|
+
status: 'error',
|
|
665
|
+
metrics: {},
|
|
666
|
+
errorMessage: 'Test error',
|
|
667
|
+
},
|
|
668
|
+
];
|
|
669
|
+
|
|
670
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
671
|
+
|
|
672
|
+
const status = screen.getByTestId('run-status-run-1');
|
|
673
|
+
expect(status).toHaveTextContent('error');
|
|
674
|
+
expect(status).toHaveStyle({ color: '#ff5555' });
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('should display running status with yellow color', () => {
|
|
678
|
+
const runs: RunStatusEntry[] = [
|
|
679
|
+
{
|
|
680
|
+
runId: 'run-1',
|
|
681
|
+
startTime: Date.now(),
|
|
682
|
+
endTime: Date.now() + 1000,
|
|
683
|
+
duration: 1000,
|
|
684
|
+
status: 'running',
|
|
685
|
+
metrics: {},
|
|
686
|
+
},
|
|
687
|
+
];
|
|
688
|
+
|
|
689
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
690
|
+
|
|
691
|
+
const status = screen.getByTestId('run-status-run-1');
|
|
692
|
+
expect(status).toHaveTextContent('running');
|
|
693
|
+
expect(status).toHaveStyle({ color: '#f1fa8c' });
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe('Metrics', () => {
|
|
698
|
+
it('should display tokens used', () => {
|
|
699
|
+
const runs: RunStatusEntry[] = [
|
|
700
|
+
{
|
|
701
|
+
runId: 'run-1',
|
|
702
|
+
startTime: Date.now(),
|
|
703
|
+
endTime: Date.now() + 1000,
|
|
704
|
+
duration: 1000,
|
|
705
|
+
status: 'completed',
|
|
706
|
+
metrics: { tokensUsed: 500 },
|
|
707
|
+
},
|
|
708
|
+
];
|
|
709
|
+
|
|
710
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
711
|
+
|
|
712
|
+
expect(screen.getByTestId('run-tokens-run-1')).toHaveTextContent('500');
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should display cost in USD', () => {
|
|
716
|
+
const runs: RunStatusEntry[] = [
|
|
717
|
+
{
|
|
718
|
+
runId: 'run-1',
|
|
719
|
+
startTime: Date.now(),
|
|
720
|
+
endTime: Date.now() + 1000,
|
|
721
|
+
duration: 1000,
|
|
722
|
+
status: 'completed',
|
|
723
|
+
metrics: { costUsd: 0.0123 },
|
|
724
|
+
},
|
|
725
|
+
];
|
|
726
|
+
|
|
727
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
728
|
+
|
|
729
|
+
expect(screen.getByTestId('run-cost-run-1')).toHaveTextContent('$0.0123');
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('should display artifacts produced', () => {
|
|
733
|
+
const runs: RunStatusEntry[] = [
|
|
734
|
+
{
|
|
735
|
+
runId: 'run-1',
|
|
736
|
+
startTime: Date.now(),
|
|
737
|
+
endTime: Date.now() + 1000,
|
|
738
|
+
duration: 1000,
|
|
739
|
+
status: 'completed',
|
|
740
|
+
metrics: { artifactsProduced: 3 },
|
|
741
|
+
},
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
745
|
+
|
|
746
|
+
expect(screen.getByTestId('run-artifacts-run-1')).toHaveTextContent('3');
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('should show dash for missing metrics', () => {
|
|
750
|
+
const runs: RunStatusEntry[] = [
|
|
751
|
+
{
|
|
752
|
+
runId: 'run-1',
|
|
753
|
+
startTime: Date.now(),
|
|
754
|
+
endTime: Date.now() + 1000,
|
|
755
|
+
duration: 1000,
|
|
756
|
+
status: 'completed',
|
|
757
|
+
metrics: {},
|
|
758
|
+
},
|
|
759
|
+
];
|
|
760
|
+
|
|
761
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
762
|
+
|
|
763
|
+
expect(screen.getByTestId('run-tokens-run-1')).toHaveTextContent('-');
|
|
764
|
+
expect(screen.getByTestId('run-cost-run-1')).toHaveTextContent('-');
|
|
765
|
+
expect(screen.getByTestId('run-artifacts-run-1')).toHaveTextContent('-');
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('should display all metrics together', () => {
|
|
769
|
+
const runs: RunStatusEntry[] = [
|
|
770
|
+
{
|
|
771
|
+
runId: 'run-1',
|
|
772
|
+
startTime: Date.now(),
|
|
773
|
+
endTime: Date.now() + 1000,
|
|
774
|
+
duration: 1000,
|
|
775
|
+
status: 'completed',
|
|
776
|
+
metrics: {
|
|
777
|
+
tokensUsed: 750,
|
|
778
|
+
costUsd: 0.025,
|
|
779
|
+
artifactsProduced: 5,
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
];
|
|
783
|
+
|
|
784
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
785
|
+
|
|
786
|
+
expect(screen.getByTestId('run-tokens-run-1')).toHaveTextContent('750');
|
|
787
|
+
expect(screen.getByTestId('run-cost-run-1')).toHaveTextContent('$0.0250');
|
|
788
|
+
expect(screen.getByTestId('run-artifacts-run-1')).toHaveTextContent('5');
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
describe('Multiple Runs', () => {
|
|
793
|
+
it('should display multiple runs in order', () => {
|
|
794
|
+
const runs: RunStatusEntry[] = [
|
|
795
|
+
{
|
|
796
|
+
runId: 'run-1',
|
|
797
|
+
startTime: Date.now(),
|
|
798
|
+
endTime: Date.now() + 1000,
|
|
799
|
+
duration: 1000,
|
|
800
|
+
status: 'completed',
|
|
801
|
+
metrics: {},
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
runId: 'run-2',
|
|
805
|
+
startTime: Date.now() + 2000,
|
|
806
|
+
endTime: Date.now() + 3000,
|
|
807
|
+
duration: 1000,
|
|
808
|
+
status: 'completed',
|
|
809
|
+
metrics: {},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
runId: 'run-3',
|
|
813
|
+
startTime: Date.now() + 4000,
|
|
814
|
+
endTime: Date.now() + 5000,
|
|
815
|
+
duration: 1000,
|
|
816
|
+
status: 'running',
|
|
817
|
+
metrics: {},
|
|
818
|
+
},
|
|
819
|
+
];
|
|
820
|
+
|
|
821
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
822
|
+
|
|
823
|
+
expect(screen.getByTestId('run-row-run-1')).toBeInTheDocument();
|
|
824
|
+
expect(screen.getByTestId('run-row-run-2')).toBeInTheDocument();
|
|
825
|
+
expect(screen.getByTestId('run-row-run-3')).toBeInTheDocument();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('should show runs with different statuses', () => {
|
|
829
|
+
const runs: RunStatusEntry[] = [
|
|
830
|
+
{
|
|
831
|
+
runId: 'run-1',
|
|
832
|
+
startTime: Date.now(),
|
|
833
|
+
endTime: Date.now() + 1000,
|
|
834
|
+
duration: 1000,
|
|
835
|
+
status: 'completed',
|
|
836
|
+
metrics: {},
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
runId: 'run-2',
|
|
840
|
+
startTime: Date.now() + 2000,
|
|
841
|
+
endTime: Date.now() + 3000,
|
|
842
|
+
duration: 1000,
|
|
843
|
+
status: 'error',
|
|
844
|
+
metrics: {},
|
|
845
|
+
},
|
|
846
|
+
];
|
|
847
|
+
|
|
848
|
+
render(<MockRunStatusTab nodeId="agent-1" runs={runs} />);
|
|
849
|
+
|
|
850
|
+
expect(screen.getByTestId('run-status-run-1')).toHaveTextContent('completed');
|
|
851
|
+
expect(screen.getByTestId('run-status-run-2')).toHaveTextContent('error');
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
describe('Tab Switching', () => {
|
|
857
|
+
beforeEach(() => {
|
|
858
|
+
useUIStore.setState({
|
|
859
|
+
mode: 'agent',
|
|
860
|
+
selectedNodeIds: new Set(),
|
|
861
|
+
detailWindows: new Map(),
|
|
862
|
+
defaultTab: 'liveOutput',
|
|
863
|
+
layoutDirection: 'TB',
|
|
864
|
+
autoLayoutEnabled: true,
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('should switch between tabs', () => {
|
|
869
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
870
|
+
|
|
871
|
+
const { rerender } = render(
|
|
872
|
+
<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// Initially on liveOutput
|
|
876
|
+
expect(screen.getByTestId('tab-live-output')).toHaveAttribute('data-active', 'true');
|
|
877
|
+
expect(screen.getByTestId('live-output-content')).toBeInTheDocument();
|
|
878
|
+
|
|
879
|
+
// Switch to message history
|
|
880
|
+
fireEvent.click(screen.getByTestId('tab-message-history'));
|
|
881
|
+
rerender(<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />);
|
|
882
|
+
|
|
883
|
+
expect(screen.getByTestId('tab-message-history')).toHaveAttribute('data-active', 'true');
|
|
884
|
+
expect(screen.getByTestId('message-history-agent-1')).toBeInTheDocument();
|
|
885
|
+
|
|
886
|
+
// Switch to run status
|
|
887
|
+
fireEvent.click(screen.getByTestId('tab-run-status'));
|
|
888
|
+
rerender(<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />);
|
|
889
|
+
|
|
890
|
+
expect(screen.getByTestId('tab-run-status')).toHaveAttribute('data-active', 'true');
|
|
891
|
+
expect(screen.getByTestId('run-status-agent-1')).toBeInTheDocument();
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it('should highlight active tab', () => {
|
|
895
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
896
|
+
render(<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />);
|
|
897
|
+
|
|
898
|
+
const liveOutputButton = screen.getByTestId('tab-live-output');
|
|
899
|
+
const messageHistoryButton = screen.getByTestId('tab-message-history');
|
|
900
|
+
|
|
901
|
+
expect(liveOutputButton).toHaveStyle({ fontWeight: 'bold' });
|
|
902
|
+
expect(messageHistoryButton).toHaveStyle({ fontWeight: 'normal' });
|
|
903
|
+
|
|
904
|
+
// Switch tab
|
|
905
|
+
fireEvent.click(messageHistoryButton);
|
|
906
|
+
|
|
907
|
+
expect(screen.getByTestId('tab-message-history')).toHaveStyle({ fontWeight: 'bold' });
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it('should persist active tab in store', () => {
|
|
911
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
912
|
+
render(<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />);
|
|
913
|
+
|
|
914
|
+
// Switch to message history
|
|
915
|
+
fireEvent.click(screen.getByTestId('tab-message-history'));
|
|
916
|
+
|
|
917
|
+
const window = useUIStore.getState().detailWindows.get('agent-1');
|
|
918
|
+
expect(window?.activeTab).toBe('messageHistory');
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it('should maintain tab state across window updates', () => {
|
|
922
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
923
|
+
|
|
924
|
+
// Set tab to messageHistory
|
|
925
|
+
useUIStore.getState().updateDetailWindow('agent-1', {
|
|
926
|
+
activeTab: 'messageHistory',
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
render(<MockTabContainer nodeId="agent-1" messages={[]} runs={[]} />);
|
|
930
|
+
|
|
931
|
+
expect(screen.getByTestId('tab-message-history')).toHaveAttribute('data-active', 'true');
|
|
932
|
+
expect(screen.getByTestId('message-history-agent-1')).toBeInTheDocument();
|
|
933
|
+
|
|
934
|
+
// Update window position (shouldn't affect tab)
|
|
935
|
+
useUIStore.getState().updateDetailWindow('agent-1', {
|
|
936
|
+
position: { x: 200, y: 200 },
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const window = useUIStore.getState().detailWindows.get('agent-1');
|
|
940
|
+
expect(window?.activeTab).toBe('messageHistory');
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
describe('Default Tab Preference', () => {
|
|
945
|
+
beforeEach(() => {
|
|
946
|
+
useUIStore.setState({
|
|
947
|
+
mode: 'agent',
|
|
948
|
+
selectedNodeIds: new Set(),
|
|
949
|
+
detailWindows: new Map(),
|
|
950
|
+
defaultTab: 'liveOutput',
|
|
951
|
+
layoutDirection: 'TB',
|
|
952
|
+
autoLayoutEnabled: true,
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it('should use liveOutput as default tab', () => {
|
|
957
|
+
useUIStore.setState({ defaultTab: 'liveOutput' });
|
|
958
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
959
|
+
|
|
960
|
+
const window = useUIStore.getState().detailWindows.get('agent-1');
|
|
961
|
+
expect(window?.activeTab).toBe('liveOutput');
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it('should use messageHistory as default tab when configured', () => {
|
|
965
|
+
useUIStore.setState({ defaultTab: 'messageHistory' });
|
|
966
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
967
|
+
|
|
968
|
+
const window = useUIStore.getState().detailWindows.get('agent-1');
|
|
969
|
+
expect(window?.activeTab).toBe('messageHistory');
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
it('should use runStatus as default tab when configured', () => {
|
|
973
|
+
useUIStore.setState({ defaultTab: 'runStatus' });
|
|
974
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
975
|
+
|
|
976
|
+
const window = useUIStore.getState().detailWindows.get('agent-1');
|
|
977
|
+
expect(window?.activeTab).toBe('runStatus');
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it('should apply default tab to newly opened windows', () => {
|
|
981
|
+
useUIStore.setState({ defaultTab: 'messageHistory' });
|
|
982
|
+
|
|
983
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
984
|
+
useUIStore.getState().openDetailWindow('agent-2');
|
|
985
|
+
useUIStore.getState().openDetailWindow('agent-3');
|
|
986
|
+
|
|
987
|
+
expect(
|
|
988
|
+
useUIStore.getState().detailWindows.get('agent-1')?.activeTab
|
|
989
|
+
).toBe('messageHistory');
|
|
990
|
+
expect(
|
|
991
|
+
useUIStore.getState().detailWindows.get('agent-2')?.activeTab
|
|
992
|
+
).toBe('messageHistory');
|
|
993
|
+
expect(
|
|
994
|
+
useUIStore.getState().detailWindows.get('agent-3')?.activeTab
|
|
995
|
+
).toBe('messageHistory');
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it('should respect default tab preference change', () => {
|
|
999
|
+
useUIStore.setState({ defaultTab: 'liveOutput' });
|
|
1000
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
1001
|
+
|
|
1002
|
+
expect(
|
|
1003
|
+
useUIStore.getState().detailWindows.get('agent-1')?.activeTab
|
|
1004
|
+
).toBe('liveOutput');
|
|
1005
|
+
|
|
1006
|
+
// Close and reopen with different preference
|
|
1007
|
+
useUIStore.getState().closeDetailWindow('agent-1');
|
|
1008
|
+
useUIStore.setState({ defaultTab: 'runStatus' });
|
|
1009
|
+
useUIStore.getState().openDetailWindow('agent-1');
|
|
1010
|
+
|
|
1011
|
+
expect(
|
|
1012
|
+
useUIStore.getState().detailWindows.get('agent-1')?.activeTab
|
|
1013
|
+
).toBe('runStatus');
|
|
1014
|
+
});
|
|
1015
|
+
});
|