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

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 (141) hide show
  1. package/README.md +380 -14
  2. package/client.log +0 -0
  3. package/dist/client/assets/{index-CM44OayM.js → index-CIvjE2Ts.js} +144 -145
  4. package/dist/client/assets/index-CIvjE2Ts.js.map +1 -0
  5. package/dist/client/assets/index-Cu3CHOQw.css +1 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/server/index.js +10 -2
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/server/routes/ai.js +2 -2
  10. package/dist/server/routes/ai.js.map +1 -1
  11. package/dist/server/routes/execute.js +181 -14
  12. package/dist/server/routes/execute.js.map +1 -1
  13. package/dist/server/services/AIService.js +39 -2
  14. package/dist/server/services/AIService.js.map +1 -1
  15. package/dist/server/services/ExecutionManager.js +428 -0
  16. package/dist/server/services/ExecutionManager.js.map +1 -0
  17. package/package.json +2 -4
  18. package/server.log +0 -0
  19. package/tests/integration/fixtures/test-workflow.md +6 -0
  20. package/.marktoflow/state/workflow-state.db +0 -0
  21. package/.marktoflow/state/workflow-state.db-shm +0 -0
  22. package/.marktoflow/state/workflow-state.db-wal +0 -0
  23. package/.turbo/turbo-build.log +0 -42
  24. package/.turbo/turbo-test.log +0 -38
  25. package/dist/client/assets/index-CM44OayM.js.map +0 -1
  26. package/dist/client/assets/index-Dru63gi6.css +0 -1
  27. package/marktoflow-gui-2.0.0-alpha.12.tgz +0 -0
  28. package/playwright.config.ts +0 -27
  29. package/postcss.config.js +0 -6
  30. package/src/client/App.tsx +0 -520
  31. package/src/client/components/Canvas/Canvas.tsx +0 -425
  32. package/src/client/components/Canvas/ExecutionOverlay.tsx +0 -935
  33. package/src/client/components/Canvas/ForEachNode.tsx +0 -152
  34. package/src/client/components/Canvas/IfElseNode.tsx +0 -141
  35. package/src/client/components/Canvas/NodeContextMenu.tsx +0 -192
  36. package/src/client/components/Canvas/OutputNode.tsx +0 -111
  37. package/src/client/components/Canvas/ParallelNode.tsx +0 -157
  38. package/src/client/components/Canvas/StepNode.tsx +0 -106
  39. package/src/client/components/Canvas/SubWorkflowNode.tsx +0 -141
  40. package/src/client/components/Canvas/SwitchNode.tsx +0 -185
  41. package/src/client/components/Canvas/Toolbar.tsx +0 -227
  42. package/src/client/components/Canvas/TransformNode.tsx +0 -194
  43. package/src/client/components/Canvas/TriggerNode.tsx +0 -128
  44. package/src/client/components/Canvas/TryCatchNode.tsx +0 -164
  45. package/src/client/components/Canvas/WhileNode.tsx +0 -161
  46. package/src/client/components/Canvas/index.ts +0 -24
  47. package/src/client/components/Debug/VariableInspector.tsx +0 -148
  48. package/src/client/components/Editor/InputsEditor.tsx +0 -458
  49. package/src/client/components/Editor/NewStepWizard.tsx +0 -344
  50. package/src/client/components/Editor/StepEditor.tsx +0 -532
  51. package/src/client/components/Editor/YamlEditor.tsx +0 -160
  52. package/src/client/components/Panels/PropertiesPanel.tsx +0 -589
  53. package/src/client/components/Prompt/ChangePreview.tsx +0 -281
  54. package/src/client/components/Prompt/PromptHistoryPanel.tsx +0 -209
  55. package/src/client/components/Prompt/PromptInput.tsx +0 -110
  56. package/src/client/components/Settings/ProviderSwitcher.tsx +0 -228
  57. package/src/client/components/Sidebar/ImportDialog.tsx +0 -257
  58. package/src/client/components/Sidebar/Sidebar.tsx +0 -362
  59. package/src/client/components/common/Breadcrumb.tsx +0 -40
  60. package/src/client/components/common/Button.tsx +0 -68
  61. package/src/client/components/common/ContextMenu.tsx +0 -202
  62. package/src/client/components/common/KeyboardShortcuts.tsx +0 -149
  63. package/src/client/components/common/Modal.tsx +0 -93
  64. package/src/client/components/common/Tabs.tsx +0 -57
  65. package/src/client/components/common/ThemeToggle.tsx +0 -63
  66. package/src/client/components/index.ts +0 -32
  67. package/src/client/hooks/index.ts +0 -4
  68. package/src/client/hooks/useAIPrompt.ts +0 -108
  69. package/src/client/hooks/useCanvas.ts +0 -247
  70. package/src/client/hooks/useWebSocket.ts +0 -164
  71. package/src/client/hooks/useWorkflow.ts +0 -138
  72. package/src/client/main.tsx +0 -10
  73. package/src/client/stores/agentStore.ts +0 -109
  74. package/src/client/stores/canvasStore.ts +0 -348
  75. package/src/client/stores/editorStore.ts +0 -133
  76. package/src/client/stores/executionStore.ts +0 -502
  77. package/src/client/stores/index.ts +0 -4
  78. package/src/client/stores/layoutStore.ts +0 -103
  79. package/src/client/stores/navigationStore.ts +0 -49
  80. package/src/client/stores/promptStore.ts +0 -113
  81. package/src/client/stores/themeStore.ts +0 -75
  82. package/src/client/stores/workflowStore.ts +0 -185
  83. package/src/client/styles/globals.css +0 -452
  84. package/src/client/utils/cn.ts +0 -9
  85. package/src/client/utils/index.ts +0 -4
  86. package/src/client/utils/platform.ts +0 -46
  87. package/src/client/utils/serviceIcons.tsx +0 -97
  88. package/src/client/utils/stepValidation.ts +0 -155
  89. package/src/client/utils/workflowToGraph.ts +0 -523
  90. package/src/server/index.ts +0 -137
  91. package/src/server/routes/ai.ts +0 -91
  92. package/src/server/routes/execute.ts +0 -71
  93. package/src/server/routes/executions.ts +0 -136
  94. package/src/server/routes/tools.ts +0 -970
  95. package/src/server/routes/workflows.ts +0 -147
  96. package/src/server/services/AIService.ts +0 -105
  97. package/src/server/services/FileWatcher.ts +0 -69
  98. package/src/server/services/WorkflowService.ts +0 -601
  99. package/src/server/services/agents/claude-code-provider.ts +0 -320
  100. package/src/server/services/agents/claude-provider.ts +0 -248
  101. package/src/server/services/agents/codex-provider.ts +0 -398
  102. package/src/server/services/agents/copilot-provider.ts +0 -311
  103. package/src/server/services/agents/demo-provider.ts +0 -184
  104. package/src/server/services/agents/index.ts +0 -31
  105. package/src/server/services/agents/ollama-provider.ts +0 -267
  106. package/src/server/services/agents/prompts.ts +0 -509
  107. package/src/server/services/agents/registry.ts +0 -310
  108. package/src/server/services/agents/types.ts +0 -146
  109. package/src/server/websocket/index.ts +0 -117
  110. package/src/shared/constants.ts +0 -180
  111. package/src/shared/types.ts +0 -179
  112. package/tailwind.config.ts +0 -73
  113. package/tests/e2e/app.spec.ts +0 -90
  114. package/tests/e2e/canvas.spec.ts +0 -128
  115. package/tests/e2e/workflow.spec.ts +0 -185
  116. package/tests/integration/api.test.ts +0 -452
  117. package/tests/integration/testApp.ts +0 -31
  118. package/tests/setup.ts +0 -72
  119. package/tests/unit/ForEachNode.test.tsx +0 -308
  120. package/tests/unit/IfElseNode.test.tsx +0 -235
  121. package/tests/unit/ParallelNode.test.tsx +0 -344
  122. package/tests/unit/SwitchNode.test.tsx +0 -327
  123. package/tests/unit/TransformNode.test.tsx +0 -386
  124. package/tests/unit/TryCatchNode.test.tsx +0 -243
  125. package/tests/unit/WhileNode.test.tsx +0 -230
  126. package/tests/unit/agentStore.test.ts +0 -218
  127. package/tests/unit/canvasStore.test.ts +0 -502
  128. package/tests/unit/codexProvider.test.ts +0 -399
  129. package/tests/unit/components.test.tsx +0 -151
  130. package/tests/unit/executionStore.test.ts +0 -567
  131. package/tests/unit/layoutStore.test.ts +0 -194
  132. package/tests/unit/navigationStore.test.ts +0 -152
  133. package/tests/unit/platform.test.ts +0 -118
  134. package/tests/unit/serviceIcons.test.ts +0 -197
  135. package/tests/unit/stepValidation.test.ts +0 -226
  136. package/tests/unit/themeStore.test.ts +0 -141
  137. package/tests/unit/workflowToGraph.test.ts +0 -311
  138. package/tsconfig.json +0 -29
  139. package/tsconfig.server.json +0 -28
  140. package/vite.config.ts +0 -31
  141. package/vitest.config.ts +0 -26
package/tests/setup.ts DELETED
@@ -1,72 +0,0 @@
1
- import '@testing-library/jest-dom';
2
- import { vi } from 'vitest';
3
-
4
- // Mock window.matchMedia
5
- Object.defineProperty(window, 'matchMedia', {
6
- writable: true,
7
- value: vi.fn().mockImplementation((query: string) => ({
8
- matches: false,
9
- media: query,
10
- onchange: null,
11
- addListener: vi.fn(),
12
- removeListener: vi.fn(),
13
- addEventListener: vi.fn(),
14
- removeEventListener: vi.fn(),
15
- dispatchEvent: vi.fn(),
16
- })),
17
- });
18
-
19
- // Mock ResizeObserver
20
- global.ResizeObserver = vi.fn().mockImplementation(() => ({
21
- observe: vi.fn(),
22
- unobserve: vi.fn(),
23
- disconnect: vi.fn(),
24
- }));
25
-
26
- // Mock IntersectionObserver
27
- global.IntersectionObserver = vi.fn().mockImplementation(() => ({
28
- observe: vi.fn(),
29
- unobserve: vi.fn(),
30
- disconnect: vi.fn(),
31
- root: null,
32
- rootMargin: '',
33
- thresholds: [],
34
- }));
35
-
36
- // Suppress console errors during tests (optional)
37
- vi.spyOn(console, 'error').mockImplementation(() => {});
38
-
39
- // Mock localStorage
40
- const localStorageMock = (() => {
41
- let store: Record<string, string> = {};
42
-
43
- return {
44
- getItem: (key: string) => store[key] || null,
45
- setItem: (key: string, value: string) => {
46
- store[key] = value.toString();
47
- },
48
- removeItem: (key: string) => {
49
- delete store[key];
50
- },
51
- clear: () => {
52
- store = {};
53
- },
54
- get length() {
55
- return Object.keys(store).length;
56
- },
57
- key: (index: number) => {
58
- const keys = Object.keys(store);
59
- return keys[index] || null;
60
- },
61
- };
62
- })();
63
-
64
- Object.defineProperty(window, 'localStorage', {
65
- value: localStorageMock,
66
- writable: true,
67
- });
68
-
69
- Object.defineProperty(window, 'sessionStorage', {
70
- value: localStorageMock,
71
- writable: true,
72
- });
@@ -1,308 +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 { ForEachNode, type ForEachNodeData } from '../../src/client/components/Canvas/ForEachNode';
5
-
6
- const createMockNode = (data: Partial<ForEachNodeData> = {}) => ({
7
- id: 'foreach-1',
8
- type: 'for_each' as const,
9
- position: { x: 0, y: 0 },
10
- data: {
11
- id: 'foreach-1',
12
- name: 'Process Orders',
13
- items: '{{ orders }}',
14
- itemVariable: 'order',
15
- status: 'pending' as const,
16
- ...data,
17
- },
18
- });
19
-
20
- const renderNode = (data: Partial<ForEachNodeData> = {}) => {
21
- const node = createMockNode(data);
22
- return render(
23
- <ReactFlowProvider>
24
- <ForEachNode
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('ForEachNode', () => {
40
- it('should render node with name, items, and variable', () => {
41
- renderNode();
42
-
43
- expect(screen.getByText('Process Orders')).toBeInTheDocument();
44
- expect(screen.getByText('{{ orders }}')).toBeInTheDocument();
45
- expect(screen.getByText('order')).toBeInTheDocument();
46
- expect(screen.getByText('Loop')).toBeInTheDocument();
47
- });
48
-
49
- it('should render default name when name is not provided', () => {
50
- renderNode({ name: undefined });
51
-
52
- expect(screen.getByText('For Each')).toBeInTheDocument();
53
- });
54
-
55
- it('should render default item variable when not provided', () => {
56
- renderNode({ itemVariable: undefined });
57
-
58
- expect(screen.getByText('item')).toBeInTheDocument();
59
- });
60
-
61
- it('should display items and variable labels', () => {
62
- renderNode();
63
-
64
- expect(screen.getByText('Items:')).toBeInTheDocument();
65
- expect(screen.getByText('Variable:')).toBeInTheDocument();
66
- });
67
-
68
- describe('status states', () => {
69
- it('should render pending status', () => {
70
- renderNode({ status: 'pending' });
71
-
72
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
73
- expect(node).toBeInTheDocument();
74
- });
75
-
76
- it('should render running status with animation', () => {
77
- renderNode({ status: 'running' });
78
-
79
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
80
- expect(node).toHaveClass('running');
81
- });
82
-
83
- it('should render completed status', () => {
84
- renderNode({ status: 'completed' });
85
-
86
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
87
- expect(node).toBeInTheDocument();
88
- });
89
-
90
- it('should render failed status', () => {
91
- renderNode({ status: 'failed' });
92
-
93
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
94
- expect(node).toBeInTheDocument();
95
- });
96
- });
97
-
98
- describe('iteration progress', () => {
99
- it('should display progress when totalIterations is provided', () => {
100
- renderNode({
101
- currentIteration: 5,
102
- totalIterations: 10,
103
- });
104
-
105
- expect(screen.getByText('Progress')).toBeInTheDocument();
106
- expect(screen.getByText('5 / 10')).toBeInTheDocument();
107
- });
108
-
109
- it('should display progress bar with correct width', () => {
110
- const { container } = renderNode({
111
- currentIteration: 5,
112
- totalIterations: 10,
113
- });
114
-
115
- const progressBar = container.querySelector('.bg-pink-400, .bg-orange-400');
116
- expect(progressBar).toBeInTheDocument();
117
- expect(progressBar).toHaveAttribute('style', expect.stringContaining('50%'));
118
- });
119
-
120
- it('should display 0% progress when currentIteration is 0', () => {
121
- const { container } = renderNode({
122
- currentIteration: 0,
123
- totalIterations: 10,
124
- });
125
-
126
- const progressBar = container.querySelector('.bg-pink-400, .bg-orange-400');
127
- expect(progressBar).toBeInTheDocument();
128
- expect(progressBar).toHaveAttribute('style', expect.stringContaining('0%'));
129
- });
130
-
131
- it('should display 100% progress when completed', () => {
132
- const { container } = renderNode({
133
- currentIteration: 10,
134
- totalIterations: 10,
135
- });
136
-
137
- const progressBar = container.querySelector('.bg-pink-400, .bg-orange-400');
138
- expect(progressBar).toBeInTheDocument();
139
- expect(progressBar).toHaveAttribute('style', expect.stringContaining('100%'));
140
- });
141
-
142
- it('should not display progress when totalIterations is undefined', () => {
143
- renderNode({ currentIteration: 5, totalIterations: undefined });
144
-
145
- expect(screen.queryByText('Progress')).not.toBeInTheDocument();
146
- });
147
- });
148
-
149
- describe('loop metadata', () => {
150
- it('should display loop metadata information', () => {
151
- renderNode();
152
-
153
- expect(screen.getByText('ℹ️')).toBeInTheDocument();
154
- expect(screen.getByText('Access: loop.index, loop.first, loop.last')).toBeInTheDocument();
155
- });
156
- });
157
-
158
- describe('visual styling', () => {
159
- it('should have pink/red gradient background', () => {
160
- renderNode();
161
-
162
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
163
- expect(node).toHaveStyle({
164
- background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
165
- });
166
- });
167
-
168
- it('should display items with monospace font', () => {
169
- renderNode();
170
-
171
- const itemsElement = screen.getByText('{{ orders }}');
172
- expect(itemsElement).toHaveClass('font-mono');
173
- });
174
-
175
- it('should display variable with monospace font', () => {
176
- renderNode();
177
-
178
- const variableElement = screen.getByText('order');
179
- expect(variableElement).toHaveClass('font-mono');
180
- });
181
- });
182
-
183
- describe('selection state', () => {
184
- it('should apply selected class when selected', () => {
185
- const node = createMockNode();
186
- const { container } = render(
187
- <ReactFlowProvider>
188
- <ForEachNode
189
- id={node.id}
190
- type={node.type}
191
- data={node.data}
192
- selected={true}
193
- isConnectable={true}
194
- zIndex={0}
195
- positionAbsoluteX={0}
196
- positionAbsoluteY={0}
197
- dragging={false}
198
- />
199
- </ReactFlowProvider>
200
- );
201
-
202
- const nodeElement = container.querySelector('.control-flow-node');
203
- expect(nodeElement).toHaveClass('selected');
204
- });
205
- });
206
-
207
- describe('items display', () => {
208
- it('should display "Not set" when items is empty', () => {
209
- renderNode({ items: '' });
210
-
211
- expect(screen.getByText('Not set')).toBeInTheDocument();
212
- });
213
-
214
- it('should display complex template expressions', () => {
215
- const complexItems = '{{ data.orders.filter(o => o.active) }}';
216
- renderNode({ items: complexItems });
217
-
218
- expect(screen.getByText(complexItems)).toBeInTheDocument();
219
- });
220
- });
221
-
222
- describe('early exit indicators', () => {
223
- it('should show early exit warning when loop exited with break', () => {
224
- renderNode({
225
- earlyExit: true,
226
- exitReason: 'break',
227
- currentIteration: 3,
228
- totalIterations: 10
229
- });
230
-
231
- expect(screen.getByText('Loop exited early (break)')).toBeInTheDocument();
232
- });
233
-
234
- it('should show error message when loop stopped on error', () => {
235
- renderNode({
236
- earlyExit: true,
237
- exitReason: 'error',
238
- currentIteration: 5,
239
- totalIterations: 10
240
- });
241
-
242
- expect(screen.getByText('Loop stopped on error')).toBeInTheDocument();
243
- });
244
-
245
- it('should show (stopped) indicator in progress text', () => {
246
- renderNode({
247
- earlyExit: true,
248
- exitReason: 'break',
249
- currentIteration: 5,
250
- totalIterations: 10
251
- });
252
-
253
- expect(screen.getByText('(stopped)')).toBeInTheDocument();
254
- expect(screen.getByText('5 / 10')).toBeInTheDocument();
255
- });
256
-
257
- it('should change progress bar color to orange when early exit', () => {
258
- const { container } = renderNode({
259
- earlyExit: true,
260
- exitReason: 'break',
261
- currentIteration: 5,
262
- totalIterations: 10
263
- });
264
-
265
- const progressBar = container.querySelector('.bg-orange-400');
266
- expect(progressBar).toBeInTheDocument();
267
- expect(progressBar).toHaveStyle({ width: '50%' });
268
- });
269
-
270
- it('should use pink color for progress bar when no early exit', () => {
271
- const { container } = renderNode({
272
- earlyExit: false,
273
- currentIteration: 5,
274
- totalIterations: 10
275
- });
276
-
277
- const progressBar = container.querySelector('.bg-pink-400');
278
- expect(progressBar).toBeInTheDocument();
279
- });
280
-
281
- it('should not show early exit warning when earlyExit is false', () => {
282
- renderNode({
283
- earlyExit: false,
284
- currentIteration: 10,
285
- totalIterations: 10
286
- });
287
-
288
- expect(screen.queryByText('Loop exited early (break)')).not.toBeInTheDocument();
289
- expect(screen.queryByText('Loop stopped on error')).not.toBeInTheDocument();
290
- });
291
- });
292
-
293
- describe('completed and failed states', () => {
294
- it('should show completed class on node', () => {
295
- renderNode({ status: 'completed' });
296
-
297
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
298
- expect(node).toHaveClass('completed');
299
- });
300
-
301
- it('should show failed class on node', () => {
302
- renderNode({ status: 'failed' });
303
-
304
- const node = screen.getByText('Process Orders').closest('.control-flow-node');
305
- expect(node).toHaveClass('failed');
306
- });
307
- });
308
- });
@@ -1,235 +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 { IfElseNode, type IfElseNodeData } from '../../src/client/components/Canvas/IfElseNode';
5
-
6
- const createMockNode = (data: Partial<IfElseNodeData> = {}) => ({
7
- id: 'if-1',
8
- type: 'if' as const,
9
- position: { x: 0, y: 0 },
10
- data: {
11
- id: 'if-1',
12
- name: 'Check Count',
13
- condition: '{{ count > 0 }}',
14
- status: 'pending' as const,
15
- ...data,
16
- },
17
- });
18
-
19
- const renderNode = (data: Partial<IfElseNodeData> = {}) => {
20
- const node = createMockNode(data);
21
- return render(
22
- <ReactFlowProvider>
23
- <IfElseNode
24
- id={node.id}
25
- type={node.type}
26
- data={node.data}
27
- selected={false}
28
- isConnectable={true}
29
- zIndex={0}
30
- positionAbsoluteX={0}
31
- positionAbsoluteY={0}
32
- dragging={false}
33
- />
34
- </ReactFlowProvider>
35
- );
36
- };
37
-
38
- describe('IfElseNode', () => {
39
- it('should render node with name and condition', () => {
40
- renderNode();
41
-
42
- expect(screen.getByText('Check Count')).toBeInTheDocument();
43
- expect(screen.getByText('{{ count > 0 }}')).toBeInTheDocument();
44
- expect(screen.getByText('Conditional')).toBeInTheDocument();
45
- });
46
-
47
- it('should render default name when name is not provided', () => {
48
- renderNode({ name: undefined });
49
-
50
- expect(screen.getByText('If/Else')).toBeInTheDocument();
51
- });
52
-
53
- it('should display condition with monospace font', () => {
54
- renderNode();
55
-
56
- const conditionElement = screen.getByText('{{ count > 0 }}');
57
- expect(conditionElement).toHaveClass('font-mono');
58
- });
59
-
60
- describe('status states', () => {
61
- it('should render pending status', () => {
62
- renderNode({ status: 'pending' });
63
-
64
- // Should have Clock icon for pending status
65
- const node = screen.getByText('Check Count').closest('.control-flow-node');
66
- expect(node).toBeInTheDocument();
67
- });
68
-
69
- it('should render running status with animation', () => {
70
- renderNode({ status: 'running' });
71
-
72
- const node = screen.getByText('Check Count').closest('.control-flow-node');
73
- expect(node).toHaveClass('running');
74
- });
75
-
76
- it('should render completed status', () => {
77
- renderNode({ status: 'completed' });
78
-
79
- // Status should be completed
80
- const node = screen.getByText('Check Count').closest('.control-flow-node');
81
- expect(node).toBeInTheDocument();
82
- });
83
-
84
- it('should render failed status', () => {
85
- renderNode({ status: 'failed' });
86
-
87
- const node = screen.getByText('Check Count').closest('.control-flow-node');
88
- expect(node).toBeInTheDocument();
89
- });
90
-
91
- it('should render skipped status', () => {
92
- renderNode({ status: 'skipped' });
93
-
94
- const node = screen.getByText('Check Count').closest('.control-flow-node');
95
- expect(node).toBeInTheDocument();
96
- });
97
- });
98
-
99
- describe('active branch highlighting', () => {
100
- it('should highlight then branch when active', () => {
101
- renderNode({ activeBranch: 'then' });
102
-
103
- const thenBranch = screen.getByText('✓ Then');
104
- expect(thenBranch).toHaveClass('bg-green-500/30', 'text-green-200');
105
- });
106
-
107
- it('should highlight else branch when active', () => {
108
- renderNode({ activeBranch: 'else' });
109
-
110
- const elseBranch = screen.getByText('✗ Else');
111
- expect(elseBranch).toHaveClass('bg-red-500/30', 'text-red-200');
112
- });
113
-
114
- it('should not highlight branches when no active branch', () => {
115
- renderNode({ activeBranch: null });
116
-
117
- const thenBranch = screen.getByText('✓ Then');
118
- const elseBranch = screen.getByText('✗ Else');
119
-
120
- expect(thenBranch).toHaveClass('bg-white/5', 'text-white/60');
121
- expect(elseBranch).toHaveClass('bg-white/5', 'text-white/60');
122
- });
123
- });
124
-
125
- describe('branch outputs', () => {
126
- it('should display both then and else branches', () => {
127
- renderNode();
128
-
129
- expect(screen.getByText('✓ Then')).toBeInTheDocument();
130
- expect(screen.getByText('✗ Else')).toBeInTheDocument();
131
- });
132
-
133
- it('should have gradient background', () => {
134
- renderNode();
135
-
136
- const node = screen.getByText('Check Count').closest('.control-flow-node');
137
- expect(node).toHaveStyle({
138
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
139
- });
140
- });
141
- });
142
-
143
- describe('selection state', () => {
144
- it('should apply selected class when selected', () => {
145
- const node = createMockNode();
146
- const { container } = render(
147
- <ReactFlowProvider>
148
- <IfElseNode
149
- id={node.id}
150
- type={node.type}
151
- data={node.data}
152
- selected={true}
153
- isConnectable={true}
154
- zIndex={0}
155
- positionAbsoluteX={0}
156
- positionAbsoluteY={0}
157
- dragging={false}
158
- />
159
- </ReactFlowProvider>
160
- );
161
-
162
- const nodeElement = container.querySelector('.control-flow-node');
163
- expect(nodeElement).toHaveClass('selected');
164
- });
165
-
166
- it('should not apply selected class when not selected', () => {
167
- const { container } = renderNode();
168
-
169
- const nodeElement = container.querySelector('.control-flow-node');
170
- expect(nodeElement).not.toHaveClass('selected');
171
- });
172
- });
173
-
174
- describe('condition display', () => {
175
- it('should display "No condition set" when condition is empty', () => {
176
- renderNode({ condition: '' });
177
-
178
- expect(screen.getByText('No condition set')).toBeInTheDocument();
179
- });
180
-
181
- it('should display complex conditions correctly', () => {
182
- const complexCondition = '{{ priority === "critical" && assignee !== null }}';
183
- renderNode({ condition: complexCondition });
184
-
185
- expect(screen.getByText(complexCondition)).toBeInTheDocument();
186
- });
187
- });
188
-
189
- describe('skipped branch indicators', () => {
190
- it('should show SKIP badge on skipped then branch', () => {
191
- renderNode({ skippedBranch: 'then' });
192
-
193
- expect(screen.getByText('SKIP')).toBeInTheDocument();
194
- const thenBranch = screen.getByText('✓ Then');
195
- expect(thenBranch).toHaveClass('bg-gray-500/20', 'text-gray-400');
196
- });
197
-
198
- it('should show SKIP badge on skipped else branch', () => {
199
- renderNode({ skippedBranch: 'else' });
200
-
201
- expect(screen.getByText('SKIP')).toBeInTheDocument();
202
- const elseBranch = screen.getByText('✗ Else');
203
- expect(elseBranch).toHaveClass('bg-gray-500/20', 'text-gray-400');
204
- });
205
-
206
- it('should highlight active branch with ring when one is active', () => {
207
- renderNode({ activeBranch: 'then' });
208
-
209
- const thenBranch = screen.getByText('✓ Then');
210
- expect(thenBranch).toHaveClass('ring-1', 'ring-green-400/50');
211
- });
212
-
213
- it('should not show SKIP when no branch is skipped', () => {
214
- renderNode({ skippedBranch: null });
215
-
216
- expect(screen.queryByText('SKIP')).not.toBeInTheDocument();
217
- });
218
- });
219
-
220
- describe('completed and failed states', () => {
221
- it('should show completed class on node', () => {
222
- renderNode({ status: 'completed' });
223
-
224
- const node = screen.getByText('Check Count').closest('.control-flow-node');
225
- expect(node).toHaveClass('completed');
226
- });
227
-
228
- it('should show failed class on node', () => {
229
- renderNode({ status: 'failed' });
230
-
231
- const node = screen.getByText('Check Count').closest('.control-flow-node');
232
- expect(node).toHaveClass('failed');
233
- });
234
- });
235
- });