@marktoflow/gui 2.0.0-alpha.12 → 2.0.0-alpha.13

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.
Files changed (121) hide show
  1. package/package.json +1 -3
  2. package/.marktoflow/state/workflow-state.db +0 -0
  3. package/.marktoflow/state/workflow-state.db-shm +0 -0
  4. package/.marktoflow/state/workflow-state.db-wal +0 -0
  5. package/.turbo/turbo-build.log +0 -42
  6. package/.turbo/turbo-test.log +0 -38
  7. package/marktoflow-gui-2.0.0-alpha.12.tgz +0 -0
  8. package/playwright.config.ts +0 -27
  9. package/postcss.config.js +0 -6
  10. package/src/client/App.tsx +0 -520
  11. package/src/client/components/Canvas/Canvas.tsx +0 -425
  12. package/src/client/components/Canvas/ExecutionOverlay.tsx +0 -935
  13. package/src/client/components/Canvas/ForEachNode.tsx +0 -152
  14. package/src/client/components/Canvas/IfElseNode.tsx +0 -141
  15. package/src/client/components/Canvas/NodeContextMenu.tsx +0 -192
  16. package/src/client/components/Canvas/OutputNode.tsx +0 -111
  17. package/src/client/components/Canvas/ParallelNode.tsx +0 -157
  18. package/src/client/components/Canvas/StepNode.tsx +0 -106
  19. package/src/client/components/Canvas/SubWorkflowNode.tsx +0 -141
  20. package/src/client/components/Canvas/SwitchNode.tsx +0 -185
  21. package/src/client/components/Canvas/Toolbar.tsx +0 -227
  22. package/src/client/components/Canvas/TransformNode.tsx +0 -194
  23. package/src/client/components/Canvas/TriggerNode.tsx +0 -128
  24. package/src/client/components/Canvas/TryCatchNode.tsx +0 -164
  25. package/src/client/components/Canvas/WhileNode.tsx +0 -161
  26. package/src/client/components/Canvas/index.ts +0 -24
  27. package/src/client/components/Debug/VariableInspector.tsx +0 -148
  28. package/src/client/components/Editor/InputsEditor.tsx +0 -458
  29. package/src/client/components/Editor/NewStepWizard.tsx +0 -344
  30. package/src/client/components/Editor/StepEditor.tsx +0 -532
  31. package/src/client/components/Editor/YamlEditor.tsx +0 -160
  32. package/src/client/components/Panels/PropertiesPanel.tsx +0 -589
  33. package/src/client/components/Prompt/ChangePreview.tsx +0 -281
  34. package/src/client/components/Prompt/PromptHistoryPanel.tsx +0 -209
  35. package/src/client/components/Prompt/PromptInput.tsx +0 -110
  36. package/src/client/components/Settings/ProviderSwitcher.tsx +0 -228
  37. package/src/client/components/Sidebar/ImportDialog.tsx +0 -257
  38. package/src/client/components/Sidebar/Sidebar.tsx +0 -362
  39. package/src/client/components/common/Breadcrumb.tsx +0 -40
  40. package/src/client/components/common/Button.tsx +0 -68
  41. package/src/client/components/common/ContextMenu.tsx +0 -202
  42. package/src/client/components/common/KeyboardShortcuts.tsx +0 -149
  43. package/src/client/components/common/Modal.tsx +0 -93
  44. package/src/client/components/common/Tabs.tsx +0 -57
  45. package/src/client/components/common/ThemeToggle.tsx +0 -63
  46. package/src/client/components/index.ts +0 -32
  47. package/src/client/hooks/index.ts +0 -4
  48. package/src/client/hooks/useAIPrompt.ts +0 -108
  49. package/src/client/hooks/useCanvas.ts +0 -247
  50. package/src/client/hooks/useWebSocket.ts +0 -164
  51. package/src/client/hooks/useWorkflow.ts +0 -138
  52. package/src/client/main.tsx +0 -10
  53. package/src/client/stores/agentStore.ts +0 -109
  54. package/src/client/stores/canvasStore.ts +0 -348
  55. package/src/client/stores/editorStore.ts +0 -133
  56. package/src/client/stores/executionStore.ts +0 -502
  57. package/src/client/stores/index.ts +0 -4
  58. package/src/client/stores/layoutStore.ts +0 -103
  59. package/src/client/stores/navigationStore.ts +0 -49
  60. package/src/client/stores/promptStore.ts +0 -113
  61. package/src/client/stores/themeStore.ts +0 -75
  62. package/src/client/stores/workflowStore.ts +0 -185
  63. package/src/client/styles/globals.css +0 -452
  64. package/src/client/utils/cn.ts +0 -9
  65. package/src/client/utils/index.ts +0 -4
  66. package/src/client/utils/platform.ts +0 -46
  67. package/src/client/utils/serviceIcons.tsx +0 -97
  68. package/src/client/utils/stepValidation.ts +0 -155
  69. package/src/client/utils/workflowToGraph.ts +0 -523
  70. package/src/server/index.ts +0 -137
  71. package/src/server/routes/ai.ts +0 -91
  72. package/src/server/routes/execute.ts +0 -71
  73. package/src/server/routes/executions.ts +0 -136
  74. package/src/server/routes/tools.ts +0 -970
  75. package/src/server/routes/workflows.ts +0 -147
  76. package/src/server/services/AIService.ts +0 -105
  77. package/src/server/services/FileWatcher.ts +0 -69
  78. package/src/server/services/WorkflowService.ts +0 -601
  79. package/src/server/services/agents/claude-code-provider.ts +0 -320
  80. package/src/server/services/agents/claude-provider.ts +0 -248
  81. package/src/server/services/agents/codex-provider.ts +0 -398
  82. package/src/server/services/agents/copilot-provider.ts +0 -311
  83. package/src/server/services/agents/demo-provider.ts +0 -184
  84. package/src/server/services/agents/index.ts +0 -31
  85. package/src/server/services/agents/ollama-provider.ts +0 -267
  86. package/src/server/services/agents/prompts.ts +0 -509
  87. package/src/server/services/agents/registry.ts +0 -310
  88. package/src/server/services/agents/types.ts +0 -146
  89. package/src/server/websocket/index.ts +0 -117
  90. package/src/shared/constants.ts +0 -180
  91. package/src/shared/types.ts +0 -179
  92. package/tailwind.config.ts +0 -73
  93. package/tests/e2e/app.spec.ts +0 -90
  94. package/tests/e2e/canvas.spec.ts +0 -128
  95. package/tests/e2e/workflow.spec.ts +0 -185
  96. package/tests/integration/api.test.ts +0 -452
  97. package/tests/integration/testApp.ts +0 -31
  98. package/tests/setup.ts +0 -72
  99. package/tests/unit/ForEachNode.test.tsx +0 -308
  100. package/tests/unit/IfElseNode.test.tsx +0 -235
  101. package/tests/unit/ParallelNode.test.tsx +0 -344
  102. package/tests/unit/SwitchNode.test.tsx +0 -327
  103. package/tests/unit/TransformNode.test.tsx +0 -386
  104. package/tests/unit/TryCatchNode.test.tsx +0 -243
  105. package/tests/unit/WhileNode.test.tsx +0 -230
  106. package/tests/unit/agentStore.test.ts +0 -218
  107. package/tests/unit/canvasStore.test.ts +0 -502
  108. package/tests/unit/codexProvider.test.ts +0 -399
  109. package/tests/unit/components.test.tsx +0 -151
  110. package/tests/unit/executionStore.test.ts +0 -567
  111. package/tests/unit/layoutStore.test.ts +0 -194
  112. package/tests/unit/navigationStore.test.ts +0 -152
  113. package/tests/unit/platform.test.ts +0 -118
  114. package/tests/unit/serviceIcons.test.ts +0 -197
  115. package/tests/unit/stepValidation.test.ts +0 -226
  116. package/tests/unit/themeStore.test.ts +0 -141
  117. package/tests/unit/workflowToGraph.test.ts +0 -311
  118. package/tsconfig.json +0 -29
  119. package/tsconfig.server.json +0 -28
  120. package/vite.config.ts +0 -31
  121. package/vitest.config.ts +0 -26
@@ -1,386 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { ReactFlowProvider } from '@xyflow/react';
4
- import { TransformNode, type TransformNodeData } from '../../src/client/components/Canvas/TransformNode';
5
-
6
- const createMockNode = (data: Partial<TransformNodeData> = {}) => ({
7
- id: 'transform-1',
8
- type: 'map' as const,
9
- position: { x: 0, y: 0 },
10
- data: {
11
- id: 'transform-1',
12
- name: 'Transform Data',
13
- transformType: 'map' as const,
14
- items: '{{ orders }}',
15
- itemVariable: 'order',
16
- expression: '{{ order.total }}',
17
- status: 'pending' as const,
18
- ...data,
19
- },
20
- });
21
-
22
- const renderNode = (data: Partial<TransformNodeData> = {}) => {
23
- const node = createMockNode(data);
24
- return render(
25
- <ReactFlowProvider>
26
- <TransformNode
27
- id={node.id}
28
- type={node.type}
29
- data={node.data}
30
- selected={false}
31
- isConnectable={true}
32
- zIndex={0}
33
- positionAbsoluteX={0}
34
- positionAbsoluteY={0}
35
- dragging={false}
36
- />
37
- </ReactFlowProvider>
38
- );
39
- };
40
-
41
- describe('TransformNode', () => {
42
- it('should render node with name and transform type', () => {
43
- renderNode();
44
-
45
- expect(screen.getByText('Transform Data')).toBeInTheDocument();
46
- expect(screen.getByText('Transform')).toBeInTheDocument();
47
- });
48
-
49
- describe('transform types', () => {
50
- it('should render map type with default name', () => {
51
- renderNode({ transformType: 'map', name: undefined });
52
-
53
- expect(screen.getByText('Map')).toBeInTheDocument();
54
- });
55
-
56
- it('should render filter type with default name', () => {
57
- renderNode({ transformType: 'filter', name: undefined });
58
-
59
- expect(screen.getByText('Filter')).toBeInTheDocument();
60
- });
61
-
62
- it('should render reduce type with default name', () => {
63
- renderNode({ transformType: 'reduce', name: undefined });
64
-
65
- expect(screen.getByText('Reduce')).toBeInTheDocument();
66
- });
67
-
68
- it('should display map description', () => {
69
- renderNode({ transformType: 'map' });
70
-
71
- expect(screen.getByText('Transforms each item')).toBeInTheDocument();
72
- });
73
-
74
- it('should display filter description', () => {
75
- renderNode({ transformType: 'filter' });
76
-
77
- expect(screen.getByText('Selects matching items')).toBeInTheDocument();
78
- });
79
-
80
- it('should display reduce description', () => {
81
- renderNode({ transformType: 'reduce' });
82
-
83
- expect(screen.getByText('Aggregates to single value')).toBeInTheDocument();
84
- });
85
- });
86
-
87
- describe('status states', () => {
88
- it('should render pending status', () => {
89
- renderNode({ status: 'pending' });
90
-
91
- const node = screen.getByText('Transform Data').closest('.control-flow-node');
92
- expect(node).toBeInTheDocument();
93
- });
94
-
95
- it('should render running status with animation', () => {
96
- renderNode({ status: 'running' });
97
-
98
- const node = screen.getByText('Transform Data').closest('.control-flow-node');
99
- expect(node).toHaveClass('running');
100
- });
101
-
102
- it('should render completed status', () => {
103
- renderNode({ status: 'completed' });
104
-
105
- const node = screen.getByText('Transform Data').closest('.control-flow-node');
106
- expect(node).toBeInTheDocument();
107
- });
108
- });
109
-
110
- describe('items and variable display', () => {
111
- it('should display items source', () => {
112
- renderNode({ items: '{{ orders }}' });
113
-
114
- expect(screen.getByText('Items:')).toBeInTheDocument();
115
- expect(screen.getByText('{{ orders }}')).toBeInTheDocument();
116
- });
117
-
118
- it('should display "Not set" when items is empty', () => {
119
- renderNode({ items: '' });
120
-
121
- expect(screen.getByText('Not set')).toBeInTheDocument();
122
- });
123
-
124
- it('should display item variable', () => {
125
- renderNode({ itemVariable: 'order' });
126
-
127
- expect(screen.getByText('Variable:')).toBeInTheDocument();
128
- expect(screen.getByText('order')).toBeInTheDocument();
129
- });
130
-
131
- it('should display default variable when not provided', () => {
132
- renderNode({ itemVariable: undefined });
133
-
134
- expect(screen.getByText('item')).toBeInTheDocument();
135
- });
136
- });
137
-
138
- describe('expression/condition display', () => {
139
- it('should display expression for map type', () => {
140
- renderNode({
141
- transformType: 'map',
142
- expression: '{{ order.total }}',
143
- });
144
-
145
- expect(screen.getByText('Expression')).toBeInTheDocument();
146
- expect(screen.getByText('{{ order.total }}')).toBeInTheDocument();
147
- });
148
-
149
- it('should display condition for filter type', () => {
150
- renderNode({
151
- transformType: 'filter',
152
- condition: '{{ item.price > 100 }}',
153
- });
154
-
155
- expect(screen.getByText('Condition')).toBeInTheDocument();
156
- expect(screen.getByText('{{ item.price > 100 }}')).toBeInTheDocument();
157
- });
158
-
159
- it('should display reducer for reduce type', () => {
160
- renderNode({
161
- transformType: 'reduce',
162
- expression: '{{ acc + item }}',
163
- accumulatorVariable: 'acc',
164
- });
165
-
166
- expect(screen.getByText('Reducer')).toBeInTheDocument();
167
- expect(screen.getByText('acc: {{ acc + item }}')).toBeInTheDocument();
168
- });
169
-
170
- it('should display "Not set" when expression is missing', () => {
171
- renderNode({
172
- transformType: 'map',
173
- expression: undefined,
174
- });
175
-
176
- expect(screen.getByText('Not set')).toBeInTheDocument();
177
- });
178
- });
179
-
180
- describe('reduce-specific fields', () => {
181
- it('should display accumulator variable for reduce', () => {
182
- renderNode({
183
- transformType: 'reduce',
184
- accumulatorVariable: 'sum',
185
- expression: '{{ sum + item.value }}',
186
- });
187
-
188
- expect(screen.getByText('sum: {{ sum + item.value }}')).toBeInTheDocument();
189
- });
190
-
191
- it('should display initial value when provided', () => {
192
- renderNode({
193
- transformType: 'reduce',
194
- initialValue: 0,
195
- });
196
-
197
- expect(screen.getByText('Initial:')).toBeInTheDocument();
198
- expect(screen.getByText('0')).toBeInTheDocument();
199
- });
200
-
201
- it('should display complex initial values', () => {
202
- renderNode({
203
- transformType: 'reduce',
204
- initialValue: { count: 0, total: 0 },
205
- });
206
-
207
- expect(screen.getByText('{"count":0,"total":0}')).toBeInTheDocument();
208
- });
209
-
210
- it('should not display initial value for non-reduce types', () => {
211
- renderNode({
212
- transformType: 'map',
213
- initialValue: 0,
214
- });
215
-
216
- expect(screen.queryByText('Initial:')).not.toBeInTheDocument();
217
- });
218
- });
219
-
220
- describe('input/output count', () => {
221
- it('should display input and output counts when provided', () => {
222
- renderNode({
223
- inputCount: 10,
224
- outputCount: 5,
225
- });
226
-
227
- expect(screen.getByText('In:')).toBeInTheDocument();
228
- expect(screen.getByText('10')).toBeInTheDocument();
229
- expect(screen.getByText('Out:')).toBeInTheDocument();
230
- expect(screen.getByText('5')).toBeInTheDocument();
231
- });
232
-
233
- it('should display "?" for missing counts when at least one is defined', () => {
234
- renderNode({
235
- inputCount: 10,
236
- outputCount: undefined,
237
- });
238
-
239
- expect(screen.getByText('10')).toBeInTheDocument();
240
- // The '?' will be rendered for undefined outputCount
241
- expect(screen.getByText((content, element) => {
242
- return element?.tagName === 'SPAN' && content === '?';
243
- })).toBeInTheDocument();
244
- });
245
-
246
- it('should not display count section when both counts are undefined', () => {
247
- renderNode({
248
- inputCount: undefined,
249
- outputCount: undefined,
250
- });
251
-
252
- // Count section should not be rendered at all
253
- expect(screen.queryByText('In:')).not.toBeInTheDocument();
254
- expect(screen.queryByText('Out:')).not.toBeInTheDocument();
255
- });
256
-
257
- it('should display mixed defined/undefined counts', () => {
258
- renderNode({
259
- inputCount: 10,
260
- outputCount: undefined,
261
- });
262
-
263
- expect(screen.getByText('10')).toBeInTheDocument();
264
- expect(screen.getByText((content, element) => {
265
- return element?.tagName === 'SPAN' && content === '?';
266
- })).toBeInTheDocument();
267
- });
268
- });
269
-
270
- describe('visual styling', () => {
271
- it('should have teal/cyan gradient background', () => {
272
- renderNode();
273
-
274
- const node = screen.getByText('Transform Data').closest('.control-flow-node');
275
- expect(node).toHaveStyle({
276
- background: 'linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%)',
277
- });
278
- });
279
-
280
- it('should display items with monospace font', () => {
281
- renderNode();
282
-
283
- const itemsElement = screen.getByText('{{ orders }}');
284
- expect(itemsElement).toHaveClass('font-mono');
285
- });
286
-
287
- it('should display variable with monospace font', () => {
288
- renderNode();
289
-
290
- const variableElement = screen.getByText('order');
291
- expect(variableElement).toHaveClass('font-mono');
292
- });
293
-
294
- it('should display expression with monospace font', () => {
295
- renderNode();
296
-
297
- const expressionElement = screen.getByText('{{ order.total }}');
298
- expect(expressionElement).toHaveClass('font-mono');
299
- });
300
- });
301
-
302
- describe('selection state', () => {
303
- it('should apply selected class when selected', () => {
304
- const node = createMockNode();
305
- const { container } = render(
306
- <ReactFlowProvider>
307
- <TransformNode
308
- id={node.id}
309
- type={node.type}
310
- data={node.data}
311
- selected={true}
312
- isConnectable={true}
313
- zIndex={0}
314
- positionAbsoluteX={0}
315
- positionAbsoluteY={0}
316
- dragging={false}
317
- />
318
- </ReactFlowProvider>
319
- );
320
-
321
- const nodeElement = container.querySelector('.control-flow-node');
322
- expect(nodeElement).toHaveClass('selected');
323
- });
324
- });
325
-
326
- describe('complete transform examples', () => {
327
- it('should render complete map transformation', () => {
328
- renderNode({
329
- name: undefined,
330
- transformType: 'map',
331
- items: '{{ users }}',
332
- itemVariable: 'user',
333
- expression: '{{ user.email }}',
334
- inputCount: 100,
335
- outputCount: 100,
336
- });
337
-
338
- expect(screen.getByText('Map')).toBeInTheDocument();
339
- expect(screen.getByText('{{ users }}')).toBeInTheDocument();
340
- expect(screen.getByText('user')).toBeInTheDocument();
341
- expect(screen.getByText('{{ user.email }}')).toBeInTheDocument();
342
- const counts = screen.getAllByText('100');
343
- expect(counts).toHaveLength(2); // Both input and output are 100
344
- });
345
-
346
- it('should render complete filter transformation', () => {
347
- renderNode({
348
- name: undefined,
349
- transformType: 'filter',
350
- items: '{{ products }}',
351
- itemVariable: 'product',
352
- condition: '{{ product.inStock }}',
353
- inputCount: 50,
354
- outputCount: 30,
355
- });
356
-
357
- expect(screen.getByText('Filter')).toBeInTheDocument();
358
- expect(screen.getByText('{{ products }}')).toBeInTheDocument();
359
- expect(screen.getByText('product')).toBeInTheDocument();
360
- expect(screen.getByText('{{ product.inStock }}')).toBeInTheDocument();
361
- expect(screen.getByText('50')).toBeInTheDocument();
362
- expect(screen.getByText('30')).toBeInTheDocument();
363
- });
364
-
365
- it('should render complete reduce transformation', () => {
366
- renderNode({
367
- name: undefined,
368
- transformType: 'reduce',
369
- items: '{{ numbers }}',
370
- itemVariable: 'num',
371
- accumulatorVariable: 'sum',
372
- initialValue: 0,
373
- expression: '{{ sum + num }}',
374
- inputCount: 10,
375
- outputCount: 1,
376
- });
377
-
378
- expect(screen.getByText('Reduce')).toBeInTheDocument();
379
- expect(screen.getByText('{{ numbers }}')).toBeInTheDocument();
380
- expect(screen.getByText('num')).toBeInTheDocument();
381
- expect(screen.getByText('sum: {{ sum + num }}')).toBeInTheDocument();
382
- expect(screen.getByText('Initial:')).toBeInTheDocument();
383
- expect(screen.getByText('0')).toBeInTheDocument();
384
- });
385
- });
386
- });
@@ -1,243 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { ReactFlowProvider } from '@xyflow/react';
4
- import { TryCatchNode, type TryCatchNodeData } from '../../src/client/components/Canvas/TryCatchNode';
5
-
6
- const createMockNode = (data: Partial<TryCatchNodeData> = {}) => ({
7
- id: 'try-1',
8
- type: 'try' as const,
9
- position: { x: 0, y: 0 },
10
- data: {
11
- id: 'try-1',
12
- name: 'Resilient API',
13
- hasCatch: true,
14
- hasFinally: true,
15
- status: 'pending' as const,
16
- ...data,
17
- },
18
- });
19
-
20
- const renderNode = (data: Partial<TryCatchNodeData> = {}) => {
21
- const node = createMockNode(data);
22
- return render(
23
- <ReactFlowProvider>
24
- <TryCatchNode
25
- id={node.id}
26
- type={node.type}
27
- data={node.data}
28
- selected={false}
29
- isConnectable={true}
30
- zIndex={0}
31
- positionAbsoluteX={0}
32
- positionAbsoluteY={0}
33
- dragging={false}
34
- />
35
- </ReactFlowProvider>
36
- );
37
- };
38
-
39
- describe('TryCatchNode', () => {
40
- it('should render node with name', () => {
41
- renderNode();
42
-
43
- expect(screen.getByText('Resilient API')).toBeInTheDocument();
44
- expect(screen.getByText('Error Handling')).toBeInTheDocument();
45
- });
46
-
47
- it('should render default name when name is not provided', () => {
48
- renderNode({ name: undefined });
49
-
50
- expect(screen.getByText('Try/Catch')).toBeInTheDocument();
51
- });
52
-
53
- describe('status states', () => {
54
- it('should render pending status', () => {
55
- renderNode({ status: 'pending' });
56
-
57
- const node = screen.getByText('Resilient API').closest('.control-flow-node');
58
- expect(node).toBeInTheDocument();
59
- });
60
-
61
- it('should render running status with animation', () => {
62
- renderNode({ status: 'running' });
63
-
64
- const node = screen.getByText('Resilient API').closest('.control-flow-node');
65
- expect(node).toHaveClass('running');
66
- });
67
-
68
- it('should render completed status', () => {
69
- renderNode({ status: 'completed' });
70
-
71
- const node = screen.getByText('Resilient API').closest('.control-flow-node');
72
- expect(node).toBeInTheDocument();
73
- });
74
-
75
- it('should render failed status', () => {
76
- renderNode({ status: 'failed' });
77
-
78
- const node = screen.getByText('Resilient API').closest('.control-flow-node');
79
- expect(node).toBeInTheDocument();
80
- });
81
- });
82
-
83
- describe('branch indicators', () => {
84
- it('should always display try branch', () => {
85
- renderNode();
86
-
87
- expect(screen.getByText('✓ Try')).toBeInTheDocument();
88
- });
89
-
90
- it('should display catch branch when hasCatch is true', () => {
91
- renderNode({ hasCatch: true });
92
-
93
- expect(screen.getByText('⚠ Catch')).toBeInTheDocument();
94
- });
95
-
96
- it('should not display catch branch when hasCatch is false', () => {
97
- renderNode({ hasCatch: false });
98
-
99
- expect(screen.queryByText('⚠ Catch')).not.toBeInTheDocument();
100
- });
101
-
102
- it('should display finally branch when hasFinally is true', () => {
103
- renderNode({ hasFinally: true });
104
-
105
- expect(screen.getByText('⟳ Finally')).toBeInTheDocument();
106
- });
107
-
108
- it('should not display finally branch when hasFinally is false', () => {
109
- renderNode({ hasFinally: false });
110
-
111
- expect(screen.queryByText('⟳ Finally')).not.toBeInTheDocument();
112
- });
113
- });
114
-
115
- describe('active branch highlighting', () => {
116
- it('should highlight try branch when active', () => {
117
- renderNode({ activeBranch: 'try' });
118
-
119
- const tryBranch = screen.getByText('✓ Try');
120
- expect(tryBranch).toHaveClass('bg-blue-500/30', 'text-blue-200');
121
- });
122
-
123
- it('should highlight catch branch when active', () => {
124
- renderNode({ activeBranch: 'catch', hasCatch: true });
125
-
126
- const catchBranch = screen.getByText('⚠ Catch');
127
- expect(catchBranch).toHaveClass('bg-red-500/30', 'text-red-200');
128
- });
129
-
130
- it('should highlight finally branch when active', () => {
131
- renderNode({ activeBranch: 'finally', hasFinally: true });
132
-
133
- const finallyBranch = screen.getByText('⟳ Finally');
134
- expect(finallyBranch).toHaveClass('bg-purple-500/30', 'text-purple-200');
135
- });
136
-
137
- it('should not highlight inactive branches', () => {
138
- renderNode({ activeBranch: 'try' });
139
-
140
- const catchBranch = screen.getByText('⚠ Catch');
141
- expect(catchBranch).toHaveClass('bg-white/5', 'text-white/60');
142
- });
143
- });
144
-
145
- describe('error indicator', () => {
146
- it('should display error indicator when errorOccurred is true', () => {
147
- renderNode({ errorOccurred: true });
148
-
149
- expect(screen.getByText('Error occurred')).toBeInTheDocument();
150
- });
151
-
152
- it('should not display error indicator when errorOccurred is false', () => {
153
- renderNode({ errorOccurred: false });
154
-
155
- expect(screen.queryByText('Error occurred')).not.toBeInTheDocument();
156
- });
157
-
158
- it('should not display error indicator by default', () => {
159
- renderNode({ errorOccurred: undefined });
160
-
161
- expect(screen.queryByText('Error occurred')).not.toBeInTheDocument();
162
- });
163
- });
164
-
165
- describe('info message', () => {
166
- it('should display finally execution info', () => {
167
- renderNode();
168
-
169
- expect(screen.getByText('ℹ️')).toBeInTheDocument();
170
- expect(screen.getByText('Finally always executes')).toBeInTheDocument();
171
- });
172
- });
173
-
174
- describe('visual styling', () => {
175
- it('should have yellow/orange gradient background', () => {
176
- renderNode();
177
-
178
- const node = screen.getByText('Resilient API').closest('.control-flow-node');
179
- expect(node).toHaveStyle({
180
- background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)',
181
- });
182
- });
183
- });
184
-
185
- describe('selection state', () => {
186
- it('should apply selected class when selected', () => {
187
- const node = createMockNode();
188
- const { container } = render(
189
- <ReactFlowProvider>
190
- <TryCatchNode
191
- id={node.id}
192
- type={node.type}
193
- data={node.data}
194
- selected={true}
195
- isConnectable={true}
196
- zIndex={0}
197
- positionAbsoluteX={0}
198
- positionAbsoluteY={0}
199
- dragging={false}
200
- />
201
- </ReactFlowProvider>
202
- );
203
-
204
- const nodeElement = container.querySelector('.control-flow-node');
205
- expect(nodeElement).toHaveClass('selected');
206
- });
207
- });
208
-
209
- describe('error indicator styling', () => {
210
- it('should display error indicator with red styling', () => {
211
- renderNode({ errorOccurred: true });
212
-
213
- const errorIndicator = screen.getByText('Error occurred').closest('.p-2');
214
- expect(errorIndicator).toHaveClass('bg-red-500/20', 'border-red-500/30');
215
- });
216
- });
217
-
218
- describe('minimal configuration', () => {
219
- it('should render with only try branch', () => {
220
- renderNode({ hasCatch: false, hasFinally: false });
221
-
222
- expect(screen.getByText('✓ Try')).toBeInTheDocument();
223
- expect(screen.queryByText('⚠ Catch')).not.toBeInTheDocument();
224
- expect(screen.queryByText('⟳ Finally')).not.toBeInTheDocument();
225
- });
226
-
227
- it('should render with try and catch only', () => {
228
- renderNode({ hasCatch: true, hasFinally: false });
229
-
230
- expect(screen.getByText('✓ Try')).toBeInTheDocument();
231
- expect(screen.getByText('⚠ Catch')).toBeInTheDocument();
232
- expect(screen.queryByText('⟳ Finally')).not.toBeInTheDocument();
233
- });
234
-
235
- it('should render with try and finally only', () => {
236
- renderNode({ hasCatch: false, hasFinally: true });
237
-
238
- expect(screen.getByText('✓ Try')).toBeInTheDocument();
239
- expect(screen.queryByText('⚠ Catch')).not.toBeInTheDocument();
240
- expect(screen.getByText('⟳ Finally')).toBeInTheDocument();
241
- });
242
- });
243
- });