@pyreon/flow 0.10.0 → 0.11.0

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.
@@ -1,5 +1,5 @@
1
- import { effect } from '@pyreon/reactivity'
2
- import { describe, expect, it, vi } from 'vitest'
1
+ import { effect } from "@pyreon/reactivity"
2
+ import { describe, expect, it, vi } from "vitest"
3
3
  import {
4
4
  getBezierPath,
5
5
  getEdgePath,
@@ -8,15 +8,15 @@ import {
8
8
  getStepPath,
9
9
  getStraightPath,
10
10
  getWaypointPath,
11
- } from '../edges'
12
- import { createFlow } from '../flow'
13
- import { Position } from '../types'
11
+ } from "../edges"
12
+ import { createFlow } from "../flow"
13
+ import { Position } from "../types"
14
14
 
15
15
  // ─── Edge path math ──────────────────────────────────────────────────────────
16
16
 
17
- describe('edge paths', () => {
18
- describe('getBezierPath', () => {
19
- it('generates a valid SVG path', () => {
17
+ describe("edge paths", () => {
18
+ describe("getBezierPath", () => {
19
+ it("generates a valid SVG path", () => {
20
20
  const result = getBezierPath({
21
21
  sourceX: 0,
22
22
  sourceY: 0,
@@ -24,12 +24,12 @@ describe('edge paths', () => {
24
24
  targetY: 100,
25
25
  })
26
26
  expect(result.path).toMatch(/^M/)
27
- expect(result.path).toContain('C')
27
+ expect(result.path).toContain("C")
28
28
  expect(result.labelX).toBe(100)
29
29
  expect(result.labelY).toBe(50)
30
30
  })
31
31
 
32
- it('respects source/target positions', () => {
32
+ it("respects source/target positions", () => {
33
33
  const right = getBezierPath({
34
34
  sourceX: 0,
35
35
  sourceY: 0,
@@ -50,7 +50,7 @@ describe('edge paths', () => {
50
50
  expect(right.path).not.toBe(bottom.path)
51
51
  })
52
52
 
53
- it('handles custom curvature', () => {
53
+ it("handles custom curvature", () => {
54
54
  const low = getBezierPath({
55
55
  sourceX: 0,
56
56
  sourceY: 0,
@@ -69,22 +69,22 @@ describe('edge paths', () => {
69
69
  })
70
70
  })
71
71
 
72
- describe('getStraightPath', () => {
73
- it('generates a straight line', () => {
72
+ describe("getStraightPath", () => {
73
+ it("generates a straight line", () => {
74
74
  const result = getStraightPath({
75
75
  sourceX: 0,
76
76
  sourceY: 0,
77
77
  targetX: 100,
78
78
  targetY: 100,
79
79
  })
80
- expect(result.path).toBe('M0,0 L100,100')
80
+ expect(result.path).toBe("M0,0 L100,100")
81
81
  expect(result.labelX).toBe(50)
82
82
  expect(result.labelY).toBe(50)
83
83
  })
84
84
  })
85
85
 
86
- describe('getSmoothStepPath', () => {
87
- it('generates a valid SVG path', () => {
86
+ describe("getSmoothStepPath", () => {
87
+ it("generates a valid SVG path", () => {
88
88
  const result = getSmoothStepPath({
89
89
  sourceX: 0,
90
90
  sourceY: 0,
@@ -98,7 +98,7 @@ describe('edge paths', () => {
98
98
  expect(result.labelY).toBe(50)
99
99
  })
100
100
 
101
- it('handles all position combinations', () => {
101
+ it("handles all position combinations", () => {
102
102
  const combos = [
103
103
  { sourcePosition: Position.Right, targetPosition: Position.Top },
104
104
  { sourcePosition: Position.Bottom, targetPosition: Position.Left },
@@ -118,8 +118,8 @@ describe('edge paths', () => {
118
118
  })
119
119
  })
120
120
 
121
- describe('getStepPath', () => {
122
- it('is smoothstep with borderRadius 0', () => {
121
+ describe("getStepPath", () => {
122
+ it("is smoothstep with borderRadius 0", () => {
123
123
  const step = getStepPath({
124
124
  sourceX: 0,
125
125
  sourceY: 0,
@@ -130,67 +130,27 @@ describe('edge paths', () => {
130
130
  })
131
131
  })
132
132
 
133
- describe('getEdgePath', () => {
134
- it('routes to correct path generator', () => {
135
- const bezier = getEdgePath(
136
- 'bezier',
137
- 0,
138
- 0,
139
- Position.Right,
140
- 200,
141
- 100,
142
- Position.Left,
143
- )
144
- const straight = getEdgePath(
145
- 'straight',
146
- 0,
147
- 0,
148
- Position.Right,
149
- 200,
150
- 100,
151
- Position.Left,
152
- )
153
- const smooth = getEdgePath(
154
- 'smoothstep',
155
- 0,
156
- 0,
157
- Position.Right,
158
- 200,
159
- 100,
160
- Position.Left,
161
- )
162
- const step = getEdgePath(
163
- 'step',
164
- 0,
165
- 0,
166
- Position.Right,
167
- 200,
168
- 100,
169
- Position.Left,
170
- )
171
-
172
- expect(bezier.path).toContain('C') // bezier has control points
173
- expect(straight.path).toContain('L') // straight is a line
133
+ describe("getEdgePath", () => {
134
+ it("routes to correct path generator", () => {
135
+ const bezier = getEdgePath("bezier", 0, 0, Position.Right, 200, 100, Position.Left)
136
+ const straight = getEdgePath("straight", 0, 0, Position.Right, 200, 100, Position.Left)
137
+ const smooth = getEdgePath("smoothstep", 0, 0, Position.Right, 200, 100, Position.Left)
138
+ const step = getEdgePath("step", 0, 0, Position.Right, 200, 100, Position.Left)
139
+
140
+ expect(bezier.path).toContain("C") // bezier has control points
141
+ expect(straight.path).toContain("L") // straight is a line
174
142
  expect(smooth.path).toMatch(/^M/) // smoothstep is valid
175
143
  expect(step.path).toMatch(/^M/) // step is valid
176
144
  })
177
145
 
178
- it('defaults to bezier for unknown type', () => {
179
- const result = getEdgePath(
180
- 'unknown',
181
- 0,
182
- 0,
183
- Position.Right,
184
- 200,
185
- 100,
186
- Position.Left,
187
- )
188
- expect(result.path).toContain('C')
146
+ it("defaults to bezier for unknown type", () => {
147
+ const result = getEdgePath("unknown", 0, 0, Position.Right, 200, 100, Position.Left)
148
+ expect(result.path).toContain("C")
189
149
  })
190
150
  })
191
151
 
192
- describe('getHandlePosition', () => {
193
- it('returns correct positions', () => {
152
+ describe("getHandlePosition", () => {
153
+ it("returns correct positions", () => {
194
154
  const top = getHandlePosition(Position.Top, 0, 0, 100, 50)
195
155
  expect(top).toEqual({ x: 50, y: 0 })
196
156
 
@@ -208,249 +168,249 @@ describe('edge paths', () => {
208
168
 
209
169
  // ─── createFlow ──────────────────────────────────────────────────────────────
210
170
 
211
- describe('createFlow', () => {
212
- describe('initialization', () => {
213
- it('creates with default empty state', () => {
171
+ describe("createFlow", () => {
172
+ describe("initialization", () => {
173
+ it("creates with default empty state", () => {
214
174
  const flow = createFlow()
215
175
  expect(flow.nodes()).toEqual([])
216
176
  expect(flow.edges()).toEqual([])
217
177
  expect(flow.viewport()).toEqual({ x: 0, y: 0, zoom: 1 })
218
178
  })
219
179
 
220
- it('creates with initial nodes and edges', () => {
180
+ it("creates with initial nodes and edges", () => {
221
181
  const flow = createFlow({
222
182
  nodes: [
223
- { id: '1', position: { x: 0, y: 0 }, data: { label: 'A' } },
224
- { id: '2', position: { x: 200, y: 0 }, data: { label: 'B' } },
183
+ { id: "1", position: { x: 0, y: 0 }, data: { label: "A" } },
184
+ { id: "2", position: { x: 200, y: 0 }, data: { label: "B" } },
225
185
  ],
226
- edges: [{ source: '1', target: '2' }],
186
+ edges: [{ source: "1", target: "2" }],
227
187
  })
228
188
  expect(flow.nodes()).toHaveLength(2)
229
189
  expect(flow.edges()).toHaveLength(1)
230
190
  expect(flow.edges()[0]!.id).toBeDefined()
231
191
  })
232
192
 
233
- it('auto-generates edge ids', () => {
193
+ it("auto-generates edge ids", () => {
234
194
  const flow = createFlow({
235
195
  nodes: [
236
- { id: '1', position: { x: 0, y: 0 }, data: {} },
237
- { id: '2', position: { x: 100, y: 0 }, data: {} },
196
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
197
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
238
198
  ],
239
- edges: [{ source: '1', target: '2' }],
199
+ edges: [{ source: "1", target: "2" }],
240
200
  })
241
- expect(flow.edges()[0]!.id).toBe('e-1-2')
201
+ expect(flow.edges()[0]!.id).toBe("e-1-2")
242
202
  })
243
203
  })
244
204
 
245
- describe('node operations', () => {
246
- it('addNode adds a node', () => {
205
+ describe("node operations", () => {
206
+ it("addNode adds a node", () => {
247
207
  const flow = createFlow()
248
208
  flow.addNode({
249
- id: '1',
209
+ id: "1",
250
210
  position: { x: 0, y: 0 },
251
- data: { label: 'New' },
211
+ data: { label: "New" },
252
212
  })
253
213
  expect(flow.nodes()).toHaveLength(1)
254
- expect(flow.nodes()[0]!.id).toBe('1')
214
+ expect(flow.nodes()[0]!.id).toBe("1")
255
215
  })
256
216
 
257
- it('removeNode removes node and connected edges', () => {
217
+ it("removeNode removes node and connected edges", () => {
258
218
  const flow = createFlow({
259
219
  nodes: [
260
- { id: '1', position: { x: 0, y: 0 }, data: {} },
261
- { id: '2', position: { x: 100, y: 0 }, data: {} },
262
- { id: '3', position: { x: 200, y: 0 }, data: {} },
220
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
221
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
222
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
263
223
  ],
264
224
  edges: [
265
- { source: '1', target: '2' },
266
- { source: '2', target: '3' },
225
+ { source: "1", target: "2" },
226
+ { source: "2", target: "3" },
267
227
  ],
268
228
  })
269
229
 
270
- flow.removeNode('2')
230
+ flow.removeNode("2")
271
231
  expect(flow.nodes()).toHaveLength(2)
272
232
  expect(flow.edges()).toHaveLength(0) // both edges connected to '2'
273
233
  })
274
234
 
275
- it('updateNode updates node properties', () => {
235
+ it("updateNode updates node properties", () => {
276
236
  const flow = createFlow({
277
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Old' } }],
237
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: { label: "Old" } }],
278
238
  })
279
- flow.updateNode('1', { data: { label: 'New' } })
280
- expect(flow.getNode('1')!.data.label).toBe('New')
239
+ flow.updateNode("1", { data: { label: "New" } })
240
+ expect(flow.getNode("1")!.data.label).toBe("New")
281
241
  })
282
242
 
283
- it('updateNodePosition updates position', () => {
243
+ it("updateNodePosition updates position", () => {
284
244
  const flow = createFlow({
285
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
245
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
286
246
  })
287
- flow.updateNodePosition('1', { x: 100, y: 200 })
288
- expect(flow.getNode('1')!.position).toEqual({ x: 100, y: 200 })
247
+ flow.updateNodePosition("1", { x: 100, y: 200 })
248
+ expect(flow.getNode("1")!.position).toEqual({ x: 100, y: 200 })
289
249
  })
290
250
 
291
- it('updateNodePosition snaps to grid when enabled', () => {
251
+ it("updateNodePosition snaps to grid when enabled", () => {
292
252
  const flow = createFlow({
293
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
253
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
294
254
  snapToGrid: true,
295
255
  snapGrid: 10,
296
256
  })
297
- flow.updateNodePosition('1', { x: 13, y: 27 })
298
- expect(flow.getNode('1')!.position).toEqual({ x: 10, y: 30 })
257
+ flow.updateNodePosition("1", { x: 13, y: 27 })
258
+ expect(flow.getNode("1")!.position).toEqual({ x: 10, y: 30 })
299
259
  })
300
260
 
301
- it('getNode returns undefined for missing id', () => {
261
+ it("getNode returns undefined for missing id", () => {
302
262
  const flow = createFlow()
303
- expect(flow.getNode('missing')).toBeUndefined()
263
+ expect(flow.getNode("missing")).toBeUndefined()
304
264
  })
305
265
  })
306
266
 
307
- describe('edge operations', () => {
308
- it('addEdge adds an edge', () => {
267
+ describe("edge operations", () => {
268
+ it("addEdge adds an edge", () => {
309
269
  const flow = createFlow({
310
270
  nodes: [
311
- { id: '1', position: { x: 0, y: 0 }, data: {} },
312
- { id: '2', position: { x: 100, y: 0 }, data: {} },
271
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
272
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
313
273
  ],
314
274
  })
315
- flow.addEdge({ source: '1', target: '2' })
275
+ flow.addEdge({ source: "1", target: "2" })
316
276
  expect(flow.edges()).toHaveLength(1)
317
277
  })
318
278
 
319
- it('addEdge prevents duplicates', () => {
279
+ it("addEdge prevents duplicates", () => {
320
280
  const flow = createFlow({
321
281
  nodes: [
322
- { id: '1', position: { x: 0, y: 0 }, data: {} },
323
- { id: '2', position: { x: 100, y: 0 }, data: {} },
282
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
283
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
324
284
  ],
325
- edges: [{ source: '1', target: '2' }],
285
+ edges: [{ source: "1", target: "2" }],
326
286
  })
327
- flow.addEdge({ source: '1', target: '2' })
287
+ flow.addEdge({ source: "1", target: "2" })
328
288
  expect(flow.edges()).toHaveLength(1) // not duplicated
329
289
  })
330
290
 
331
- it('removeEdge removes an edge', () => {
291
+ it("removeEdge removes an edge", () => {
332
292
  const flow = createFlow({
333
293
  nodes: [
334
- { id: '1', position: { x: 0, y: 0 }, data: {} },
335
- { id: '2', position: { x: 100, y: 0 }, data: {} },
294
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
295
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
336
296
  ],
337
- edges: [{ source: '1', target: '2' }],
297
+ edges: [{ source: "1", target: "2" }],
338
298
  })
339
- flow.removeEdge('e-1-2')
299
+ flow.removeEdge("e-1-2")
340
300
  expect(flow.edges()).toHaveLength(0)
341
301
  })
342
302
 
343
- it('getEdge returns edge by id', () => {
303
+ it("getEdge returns edge by id", () => {
344
304
  const flow = createFlow({
345
305
  nodes: [
346
- { id: '1', position: { x: 0, y: 0 }, data: {} },
347
- { id: '2', position: { x: 100, y: 0 }, data: {} },
306
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
307
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
348
308
  ],
349
- edges: [{ id: 'my-edge', source: '1', target: '2' }],
309
+ edges: [{ id: "my-edge", source: "1", target: "2" }],
350
310
  })
351
- expect(flow.getEdge('my-edge')).toBeDefined()
352
- expect(flow.getEdge('missing')).toBeUndefined()
311
+ expect(flow.getEdge("my-edge")).toBeDefined()
312
+ expect(flow.getEdge("missing")).toBeUndefined()
353
313
  })
354
314
  })
355
315
 
356
- describe('connection rules', () => {
357
- it('validates connections based on rules', () => {
316
+ describe("connection rules", () => {
317
+ it("validates connections based on rules", () => {
358
318
  const flow = createFlow({
359
319
  nodes: [
360
- { id: '1', type: 'input', position: { x: 0, y: 0 }, data: {} },
361
- { id: '2', type: 'process', position: { x: 100, y: 0 }, data: {} },
362
- { id: '3', type: 'output', position: { x: 200, y: 0 }, data: {} },
320
+ { id: "1", type: "input", position: { x: 0, y: 0 }, data: {} },
321
+ { id: "2", type: "process", position: { x: 100, y: 0 }, data: {} },
322
+ { id: "3", type: "output", position: { x: 200, y: 0 }, data: {} },
363
323
  ],
364
324
  connectionRules: {
365
- input: { outputs: ['process'] },
366
- process: { outputs: ['process', 'output'] },
325
+ input: { outputs: ["process"] },
326
+ process: { outputs: ["process", "output"] },
367
327
  output: { outputs: [] },
368
328
  },
369
329
  })
370
330
 
371
- expect(flow.isValidConnection({ source: '1', target: '2' })).toBe(true) // input → process
372
- expect(flow.isValidConnection({ source: '2', target: '3' })).toBe(true) // process → output
373
- expect(flow.isValidConnection({ source: '1', target: '3' })).toBe(false) // input → output (blocked)
374
- expect(flow.isValidConnection({ source: '3', target: '1' })).toBe(false) // output → (no outputs)
331
+ expect(flow.isValidConnection({ source: "1", target: "2" })).toBe(true) // input → process
332
+ expect(flow.isValidConnection({ source: "2", target: "3" })).toBe(true) // process → output
333
+ expect(flow.isValidConnection({ source: "1", target: "3" })).toBe(false) // input → output (blocked)
334
+ expect(flow.isValidConnection({ source: "3", target: "1" })).toBe(false) // output → (no outputs)
375
335
  })
376
336
 
377
- it('allows all connections without rules', () => {
337
+ it("allows all connections without rules", () => {
378
338
  const flow = createFlow({
379
339
  nodes: [
380
- { id: '1', position: { x: 0, y: 0 }, data: {} },
381
- { id: '2', position: { x: 100, y: 0 }, data: {} },
340
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
341
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
382
342
  ],
383
343
  })
384
- expect(flow.isValidConnection({ source: '1', target: '2' })).toBe(true)
344
+ expect(flow.isValidConnection({ source: "1", target: "2" })).toBe(true)
385
345
  })
386
346
  })
387
347
 
388
- describe('selection', () => {
389
- it('selectNode selects a node', () => {
348
+ describe("selection", () => {
349
+ it("selectNode selects a node", () => {
390
350
  const flow = createFlow({
391
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
351
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
392
352
  })
393
- flow.selectNode('1')
394
- expect(flow.selectedNodes()).toEqual(['1'])
353
+ flow.selectNode("1")
354
+ expect(flow.selectedNodes()).toEqual(["1"])
395
355
  })
396
356
 
397
- it('selectNode replaces selection by default', () => {
357
+ it("selectNode replaces selection by default", () => {
398
358
  const flow = createFlow({
399
359
  nodes: [
400
- { id: '1', position: { x: 0, y: 0 }, data: {} },
401
- { id: '2', position: { x: 100, y: 0 }, data: {} },
360
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
361
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
402
362
  ],
403
363
  })
404
- flow.selectNode('1')
405
- flow.selectNode('2')
406
- expect(flow.selectedNodes()).toEqual(['2'])
364
+ flow.selectNode("1")
365
+ flow.selectNode("2")
366
+ expect(flow.selectedNodes()).toEqual(["2"])
407
367
  })
408
368
 
409
- it('selectNode with additive adds to selection', () => {
369
+ it("selectNode with additive adds to selection", () => {
410
370
  const flow = createFlow({
411
371
  nodes: [
412
- { id: '1', position: { x: 0, y: 0 }, data: {} },
413
- { id: '2', position: { x: 100, y: 0 }, data: {} },
372
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
373
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
414
374
  ],
415
375
  })
416
- flow.selectNode('1')
417
- flow.selectNode('2', true)
418
- expect(flow.selectedNodes()).toEqual(expect.arrayContaining(['1', '2']))
376
+ flow.selectNode("1")
377
+ flow.selectNode("2", true)
378
+ expect(flow.selectedNodes()).toEqual(expect.arrayContaining(["1", "2"]))
419
379
  })
420
380
 
421
- it('clearSelection clears all', () => {
381
+ it("clearSelection clears all", () => {
422
382
  const flow = createFlow({
423
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
383
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
424
384
  })
425
- flow.selectNode('1')
385
+ flow.selectNode("1")
426
386
  flow.clearSelection()
427
387
  expect(flow.selectedNodes()).toEqual([])
428
388
  })
429
389
 
430
- it('selectAll selects all nodes', () => {
390
+ it("selectAll selects all nodes", () => {
431
391
  const flow = createFlow({
432
392
  nodes: [
433
- { id: '1', position: { x: 0, y: 0 }, data: {} },
434
- { id: '2', position: { x: 100, y: 0 }, data: {} },
393
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
394
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
435
395
  ],
436
396
  })
437
397
  flow.selectAll()
438
398
  expect(flow.selectedNodes()).toHaveLength(2)
439
399
  })
440
400
 
441
- it('deleteSelected removes selected nodes and edges', () => {
401
+ it("deleteSelected removes selected nodes and edges", () => {
442
402
  const flow = createFlow({
443
403
  nodes: [
444
- { id: '1', position: { x: 0, y: 0 }, data: {} },
445
- { id: '2', position: { x: 100, y: 0 }, data: {} },
446
- { id: '3', position: { x: 200, y: 0 }, data: {} },
404
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
405
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
406
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
447
407
  ],
448
408
  edges: [
449
- { source: '1', target: '2' },
450
- { source: '2', target: '3' },
409
+ { source: "1", target: "2" },
410
+ { source: "2", target: "3" },
451
411
  ],
452
412
  })
453
- flow.selectNode('2')
413
+ flow.selectNode("2")
454
414
  flow.deleteSelected()
455
415
  expect(flow.nodes()).toHaveLength(2)
456
416
  expect(flow.edges()).toHaveLength(0)
@@ -458,22 +418,22 @@ describe('createFlow', () => {
458
418
  })
459
419
  })
460
420
 
461
- describe('viewport', () => {
462
- it('zoomIn increases zoom', () => {
421
+ describe("viewport", () => {
422
+ it("zoomIn increases zoom", () => {
463
423
  const flow = createFlow()
464
424
  const initial = flow.zoom()
465
425
  flow.zoomIn()
466
426
  expect(flow.zoom()).toBeGreaterThan(initial)
467
427
  })
468
428
 
469
- it('zoomOut decreases zoom', () => {
429
+ it("zoomOut decreases zoom", () => {
470
430
  const flow = createFlow()
471
431
  const initial = flow.zoom()
472
432
  flow.zoomOut()
473
433
  expect(flow.zoom()).toBeLessThan(initial)
474
434
  })
475
435
 
476
- it('zoomTo clamps to min/max', () => {
436
+ it("zoomTo clamps to min/max", () => {
477
437
  const flow = createFlow({ minZoom: 0.5, maxZoom: 2 })
478
438
  flow.zoomTo(0.1)
479
439
  expect(flow.zoom()).toBe(0.5)
@@ -481,11 +441,11 @@ describe('createFlow', () => {
481
441
  expect(flow.zoom()).toBe(2)
482
442
  })
483
443
 
484
- it('fitView adjusts viewport to show all nodes', () => {
444
+ it("fitView adjusts viewport to show all nodes", () => {
485
445
  const flow = createFlow({
486
446
  nodes: [
487
- { id: '1', position: { x: 0, y: 0 }, data: {} },
488
- { id: '2', position: { x: 500, y: 300 }, data: {} },
447
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
448
+ { id: "2", position: { x: 500, y: 300 }, data: {} },
489
449
  ],
490
450
  })
491
451
  flow.fitView()
@@ -493,22 +453,22 @@ describe('createFlow', () => {
493
453
  expect(flow.viewport().zoom).toBeGreaterThan(0)
494
454
  })
495
455
 
496
- it('fitView with specific nodes', () => {
456
+ it("fitView with specific nodes", () => {
497
457
  const flow = createFlow({
498
458
  nodes: [
499
- { id: '1', position: { x: 0, y: 0 }, data: {} },
500
- { id: '2', position: { x: 1000, y: 1000 }, data: {} },
501
- { id: '3', position: { x: 50, y: 50 }, data: {} },
459
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
460
+ { id: "2", position: { x: 1000, y: 1000 }, data: {} },
461
+ { id: "3", position: { x: 50, y: 50 }, data: {} },
502
462
  ],
503
463
  })
504
- flow.fitView(['1', '3'])
464
+ flow.fitView(["1", "3"])
505
465
  // Should zoom in more since only close nodes are targeted
506
466
  expect(flow.viewport().zoom).toBeGreaterThan(0)
507
467
  })
508
468
  })
509
469
 
510
- describe('viewport — panTo and isNodeVisible', () => {
511
- it('panTo updates viewport position', () => {
470
+ describe("viewport — panTo and isNodeVisible", () => {
471
+ it("panTo updates viewport position", () => {
512
472
  const flow = createFlow()
513
473
  flow.panTo({ x: 100, y: 200 })
514
474
  const vp = flow.viewport()
@@ -516,19 +476,19 @@ describe('createFlow', () => {
516
476
  expect(vp.y).toBe(-200)
517
477
  })
518
478
 
519
- it('isNodeVisible checks viewport bounds', () => {
479
+ it("isNodeVisible checks viewport bounds", () => {
520
480
  const flow = createFlow({
521
481
  nodes: [
522
- { id: '1', position: { x: 0, y: 0 }, data: {} },
523
- { id: '2', position: { x: 5000, y: 5000 }, data: {} },
482
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
483
+ { id: "2", position: { x: 5000, y: 5000 }, data: {} },
524
484
  ],
525
485
  })
526
- expect(flow.isNodeVisible('1')).toBe(true)
527
- expect(flow.isNodeVisible('2')).toBe(false)
528
- expect(flow.isNodeVisible('missing')).toBe(false)
486
+ expect(flow.isNodeVisible("1")).toBe(true)
487
+ expect(flow.isNodeVisible("2")).toBe(false)
488
+ expect(flow.isNodeVisible("missing")).toBe(false)
529
489
  })
530
490
 
531
- it('fitView with no nodes does nothing', () => {
491
+ it("fitView with no nodes does nothing", () => {
532
492
  const flow = createFlow()
533
493
  const before = flow.viewport()
534
494
  flow.fitView()
@@ -536,166 +496,160 @@ describe('createFlow', () => {
536
496
  })
537
497
  })
538
498
 
539
- describe('edge selection', () => {
540
- it('selectEdge selects an edge', () => {
499
+ describe("edge selection", () => {
500
+ it("selectEdge selects an edge", () => {
541
501
  const flow = createFlow({
542
502
  nodes: [
543
- { id: '1', position: { x: 0, y: 0 }, data: {} },
544
- { id: '2', position: { x: 100, y: 0 }, data: {} },
503
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
504
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
545
505
  ],
546
- edges: [{ id: 'e1', source: '1', target: '2' }],
506
+ edges: [{ id: "e1", source: "1", target: "2" }],
547
507
  })
548
- flow.selectEdge('e1')
549
- expect(flow.selectedEdges()).toEqual(['e1'])
508
+ flow.selectEdge("e1")
509
+ expect(flow.selectedEdges()).toEqual(["e1"])
550
510
  })
551
511
 
552
- it('selectEdge clears node selection by default', () => {
512
+ it("selectEdge clears node selection by default", () => {
553
513
  const flow = createFlow({
554
514
  nodes: [
555
- { id: '1', position: { x: 0, y: 0 }, data: {} },
556
- { id: '2', position: { x: 100, y: 0 }, data: {} },
515
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
516
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
557
517
  ],
558
- edges: [{ id: 'e1', source: '1', target: '2' }],
518
+ edges: [{ id: "e1", source: "1", target: "2" }],
559
519
  })
560
- flow.selectNode('1')
561
- flow.selectEdge('e1')
520
+ flow.selectNode("1")
521
+ flow.selectEdge("e1")
562
522
  expect(flow.selectedNodes()).toEqual([])
563
- expect(flow.selectedEdges()).toEqual(['e1'])
523
+ expect(flow.selectedEdges()).toEqual(["e1"])
564
524
  })
565
525
 
566
- it('selectEdge additive mode', () => {
526
+ it("selectEdge additive mode", () => {
567
527
  const flow = createFlow({
568
528
  nodes: [
569
- { id: '1', position: { x: 0, y: 0 }, data: {} },
570
- { id: '2', position: { x: 100, y: 0 }, data: {} },
571
- { id: '3', position: { x: 200, y: 0 }, data: {} },
529
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
530
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
531
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
572
532
  ],
573
533
  edges: [
574
- { id: 'e1', source: '1', target: '2' },
575
- { id: 'e2', source: '2', target: '3' },
534
+ { id: "e1", source: "1", target: "2" },
535
+ { id: "e2", source: "2", target: "3" },
576
536
  ],
577
537
  })
578
- flow.selectEdge('e1')
579
- flow.selectEdge('e2', true)
580
- expect(flow.selectedEdges()).toEqual(expect.arrayContaining(['e1', 'e2']))
538
+ flow.selectEdge("e1")
539
+ flow.selectEdge("e2", true)
540
+ expect(flow.selectedEdges()).toEqual(expect.arrayContaining(["e1", "e2"]))
581
541
  })
582
542
 
583
- it('deselectNode removes from selection', () => {
543
+ it("deselectNode removes from selection", () => {
584
544
  const flow = createFlow({
585
545
  nodes: [
586
- { id: '1', position: { x: 0, y: 0 }, data: {} },
587
- { id: '2', position: { x: 100, y: 0 }, data: {} },
546
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
547
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
588
548
  ],
589
549
  })
590
- flow.selectNode('1')
591
- flow.selectNode('2', true)
592
- flow.deselectNode('1')
593
- expect(flow.selectedNodes()).toEqual(['2'])
550
+ flow.selectNode("1")
551
+ flow.selectNode("2", true)
552
+ flow.deselectNode("1")
553
+ expect(flow.selectedNodes()).toEqual(["2"])
594
554
  })
595
555
 
596
- it('deleteSelected with selected edges only', () => {
556
+ it("deleteSelected with selected edges only", () => {
597
557
  const flow = createFlow({
598
558
  nodes: [
599
- { id: '1', position: { x: 0, y: 0 }, data: {} },
600
- { id: '2', position: { x: 100, y: 0 }, data: {} },
559
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
560
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
601
561
  ],
602
- edges: [{ id: 'e1', source: '1', target: '2' }],
562
+ edges: [{ id: "e1", source: "1", target: "2" }],
603
563
  })
604
- flow.selectEdge('e1')
564
+ flow.selectEdge("e1")
605
565
  flow.deleteSelected()
606
566
  expect(flow.edges()).toHaveLength(0)
607
567
  expect(flow.nodes()).toHaveLength(2) // nodes untouched
608
568
  })
609
569
  })
610
570
 
611
- describe('connection rules — edge cases', () => {
612
- it('returns false for missing source node', () => {
571
+ describe("connection rules — edge cases", () => {
572
+ it("returns false for missing source node", () => {
613
573
  const flow = createFlow({
614
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
615
- connectionRules: { default: { outputs: ['default'] } },
574
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
575
+ connectionRules: { default: { outputs: ["default"] } },
616
576
  })
617
- expect(flow.isValidConnection({ source: 'missing', target: '1' })).toBe(
618
- false,
619
- )
577
+ expect(flow.isValidConnection({ source: "missing", target: "1" })).toBe(false)
620
578
  })
621
579
 
622
- it('returns false for missing target node', () => {
580
+ it("returns false for missing target node", () => {
623
581
  const flow = createFlow({
624
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
625
- connectionRules: { default: { outputs: ['default'] } },
582
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
583
+ connectionRules: { default: { outputs: ["default"] } },
626
584
  })
627
- expect(flow.isValidConnection({ source: '1', target: 'missing' })).toBe(
628
- false,
629
- )
585
+ expect(flow.isValidConnection({ source: "1", target: "missing" })).toBe(false)
630
586
  })
631
587
 
632
- it('allows connection when no rule for source type', () => {
588
+ it("allows connection when no rule for source type", () => {
633
589
  const flow = createFlow({
634
590
  nodes: [
635
- { id: '1', type: 'custom', position: { x: 0, y: 0 }, data: {} },
636
- { id: '2', position: { x: 100, y: 0 }, data: {} },
591
+ { id: "1", type: "custom", position: { x: 0, y: 0 }, data: {} },
592
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
637
593
  ],
638
594
  connectionRules: { default: { outputs: [] } },
639
595
  })
640
- expect(flow.isValidConnection({ source: '1', target: '2' })).toBe(true) // no rule for 'custom'
596
+ expect(flow.isValidConnection({ source: "1", target: "2" })).toBe(true) // no rule for 'custom'
641
597
  })
642
598
  })
643
599
 
644
- describe('graph queries', () => {
645
- it('getConnectedEdges returns edges for a node', () => {
600
+ describe("graph queries", () => {
601
+ it("getConnectedEdges returns edges for a node", () => {
646
602
  const flow = createFlow({
647
603
  nodes: [
648
- { id: '1', position: { x: 0, y: 0 }, data: {} },
649
- { id: '2', position: { x: 100, y: 0 }, data: {} },
650
- { id: '3', position: { x: 200, y: 0 }, data: {} },
604
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
605
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
606
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
651
607
  ],
652
608
  edges: [
653
- { source: '1', target: '2' },
654
- { source: '2', target: '3' },
609
+ { source: "1", target: "2" },
610
+ { source: "2", target: "3" },
655
611
  ],
656
612
  })
657
- expect(flow.getConnectedEdges('2')).toHaveLength(2)
658
- expect(flow.getConnectedEdges('1')).toHaveLength(1)
613
+ expect(flow.getConnectedEdges("2")).toHaveLength(2)
614
+ expect(flow.getConnectedEdges("1")).toHaveLength(1)
659
615
  })
660
616
 
661
- it('getIncomers returns upstream nodes', () => {
617
+ it("getIncomers returns upstream nodes", () => {
662
618
  const flow = createFlow({
663
619
  nodes: [
664
- { id: '1', position: { x: 0, y: 0 }, data: {} },
665
- { id: '2', position: { x: 100, y: 0 }, data: {} },
666
- { id: '3', position: { x: 200, y: 0 }, data: {} },
620
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
621
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
622
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
667
623
  ],
668
624
  edges: [
669
- { source: '1', target: '3' },
670
- { source: '2', target: '3' },
625
+ { source: "1", target: "3" },
626
+ { source: "2", target: "3" },
671
627
  ],
672
628
  })
673
- const incomers = flow.getIncomers('3')
629
+ const incomers = flow.getIncomers("3")
674
630
  expect(incomers).toHaveLength(2)
675
- expect(incomers.map((n) => n.id)).toEqual(
676
- expect.arrayContaining(['1', '2']),
677
- )
631
+ expect(incomers.map((n) => n.id)).toEqual(expect.arrayContaining(["1", "2"]))
678
632
  })
679
633
 
680
- it('getOutgoers returns downstream nodes', () => {
634
+ it("getOutgoers returns downstream nodes", () => {
681
635
  const flow = createFlow({
682
636
  nodes: [
683
- { id: '1', position: { x: 0, y: 0 }, data: {} },
684
- { id: '2', position: { x: 100, y: 0 }, data: {} },
685
- { id: '3', position: { x: 200, y: 0 }, data: {} },
637
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
638
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
639
+ { id: "3", position: { x: 200, y: 0 }, data: {} },
686
640
  ],
687
641
  edges: [
688
- { source: '1', target: '2' },
689
- { source: '1', target: '3' },
642
+ { source: "1", target: "2" },
643
+ { source: "1", target: "3" },
690
644
  ],
691
645
  })
692
- const outgoers = flow.getOutgoers('1')
646
+ const outgoers = flow.getOutgoers("1")
693
647
  expect(outgoers).toHaveLength(2)
694
648
  })
695
649
  })
696
650
 
697
- describe('reactivity', () => {
698
- it('nodes signal is reactive in effects', () => {
651
+ describe("reactivity", () => {
652
+ it("nodes signal is reactive in effects", () => {
699
653
  const flow = createFlow()
700
654
  const counts: number[] = []
701
655
 
@@ -703,13 +657,13 @@ describe('createFlow', () => {
703
657
  counts.push(flow.nodes().length)
704
658
  })
705
659
 
706
- flow.addNode({ id: '1', position: { x: 0, y: 0 }, data: {} })
707
- flow.addNode({ id: '2', position: { x: 100, y: 0 }, data: {} })
660
+ flow.addNode({ id: "1", position: { x: 0, y: 0 }, data: {} })
661
+ flow.addNode({ id: "2", position: { x: 100, y: 0 }, data: {} })
708
662
 
709
663
  expect(counts).toEqual([0, 1, 2])
710
664
  })
711
665
 
712
- it('zoom is a reactive computed', () => {
666
+ it("zoom is a reactive computed", () => {
713
667
  const flow = createFlow()
714
668
  const zooms: number[] = []
715
669
 
@@ -723,11 +677,11 @@ describe('createFlow', () => {
723
677
  expect(zooms).toHaveLength(3)
724
678
  })
725
679
 
726
- it('selectedNodes is reactive', () => {
680
+ it("selectedNodes is reactive", () => {
727
681
  const flow = createFlow({
728
682
  nodes: [
729
- { id: '1', position: { x: 0, y: 0 }, data: {} },
730
- { id: '2', position: { x: 100, y: 0 }, data: {} },
683
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
684
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
731
685
  ],
732
686
  })
733
687
  const selections: string[][] = []
@@ -736,75 +690,71 @@ describe('createFlow', () => {
736
690
  selections.push([...flow.selectedNodes()])
737
691
  })
738
692
 
739
- flow.selectNode('1')
740
- flow.selectNode('2', true)
693
+ flow.selectNode("1")
694
+ flow.selectNode("2", true)
741
695
 
742
696
  expect(selections).toHaveLength(3)
743
- expect(selections[2]).toEqual(expect.arrayContaining(['1', '2']))
697
+ expect(selections[2]).toEqual(expect.arrayContaining(["1", "2"]))
744
698
  })
745
699
  })
746
700
 
747
- describe('listeners', () => {
748
- it('onConnect fires when edge is added', () => {
701
+ describe("listeners", () => {
702
+ it("onConnect fires when edge is added", () => {
749
703
  const flow = createFlow({
750
704
  nodes: [
751
- { id: '1', position: { x: 0, y: 0 }, data: {} },
752
- { id: '2', position: { x: 100, y: 0 }, data: {} },
705
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
706
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
753
707
  ],
754
708
  })
755
709
  const fn = vi.fn()
756
710
  flow.onConnect(fn)
757
711
 
758
- flow.addEdge({ source: '1', target: '2' })
759
- expect(fn).toHaveBeenCalledWith(
760
- expect.objectContaining({ source: '1', target: '2' }),
761
- )
712
+ flow.addEdge({ source: "1", target: "2" })
713
+ expect(fn).toHaveBeenCalledWith(expect.objectContaining({ source: "1", target: "2" }))
762
714
  })
763
715
 
764
- it('onNodesChange fires on position update', () => {
716
+ it("onNodesChange fires on position update", () => {
765
717
  const flow = createFlow({
766
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
718
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
767
719
  })
768
720
  const fn = vi.fn()
769
721
  flow.onNodesChange(fn)
770
722
 
771
- flow.updateNodePosition('1', { x: 100, y: 200 })
772
- expect(fn).toHaveBeenCalledWith([
773
- { type: 'position', id: '1', position: { x: 100, y: 200 } },
774
- ])
723
+ flow.updateNodePosition("1", { x: 100, y: 200 })
724
+ expect(fn).toHaveBeenCalledWith([{ type: "position", id: "1", position: { x: 100, y: 200 } }])
775
725
  })
776
726
 
777
- it('onNodesChange fires on remove', () => {
727
+ it("onNodesChange fires on remove", () => {
778
728
  const flow = createFlow({
779
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
729
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
780
730
  })
781
731
  const fn = vi.fn()
782
732
  flow.onNodesChange(fn)
783
733
 
784
- flow.removeNode('1')
785
- expect(fn).toHaveBeenCalledWith([{ type: 'remove', id: '1' }])
734
+ flow.removeNode("1")
735
+ expect(fn).toHaveBeenCalledWith([{ type: "remove", id: "1" }])
786
736
  })
787
737
 
788
- it('listeners can be unsubscribed', () => {
738
+ it("listeners can be unsubscribed", () => {
789
739
  const flow = createFlow({
790
740
  nodes: [
791
- { id: '1', position: { x: 0, y: 0 }, data: {} },
792
- { id: '2', position: { x: 100, y: 0 }, data: {} },
741
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
742
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
793
743
  ],
794
744
  })
795
745
  const fn = vi.fn()
796
746
  const unsub = flow.onConnect(fn)
797
747
 
798
- flow.addEdge({ source: '1', target: '2' })
748
+ flow.addEdge({ source: "1", target: "2" })
799
749
  expect(fn).toHaveBeenCalledOnce()
800
750
 
801
751
  unsub()
802
- flow.removeEdge('e-1-2')
803
- flow.addEdge({ source: '1', target: '2' })
752
+ flow.removeEdge("e-1-2")
753
+ flow.addEdge({ source: "1", target: "2" })
804
754
  expect(fn).toHaveBeenCalledOnce() // not called again
805
755
  })
806
756
 
807
- it('dispose clears all listeners', () => {
757
+ it("dispose clears all listeners", () => {
808
758
  const flow = createFlow()
809
759
  const fn1 = vi.fn()
810
760
  const fn2 = vi.fn()
@@ -813,15 +763,15 @@ describe('createFlow', () => {
813
763
 
814
764
  flow.dispose()
815
765
 
816
- flow.addNode({ id: '1', position: { x: 0, y: 0 }, data: {} })
817
- flow.addEdge({ source: '1', target: '1' })
766
+ flow.addNode({ id: "1", position: { x: 0, y: 0 }, data: {} })
767
+ flow.addEdge({ source: "1", target: "1" })
818
768
  expect(fn1).not.toHaveBeenCalled()
819
769
  expect(fn2).not.toHaveBeenCalled()
820
770
  })
821
771
  })
822
772
 
823
- describe('batch', () => {
824
- it('batches multiple operations', () => {
773
+ describe("batch", () => {
774
+ it("batches multiple operations", () => {
825
775
  const flow = createFlow()
826
776
  const counts: number[] = []
827
777
 
@@ -830,9 +780,9 @@ describe('createFlow', () => {
830
780
  })
831
781
 
832
782
  flow.batch(() => {
833
- flow.addNode({ id: '1', position: { x: 0, y: 0 }, data: {} })
834
- flow.addNode({ id: '2', position: { x: 100, y: 0 }, data: {} })
835
- flow.addNode({ id: '3', position: { x: 200, y: 0 }, data: {} })
783
+ flow.addNode({ id: "1", position: { x: 0, y: 0 }, data: {} })
784
+ flow.addNode({ id: "2", position: { x: 100, y: 0 }, data: {} })
785
+ flow.addNode({ id: "3", position: { x: 200, y: 0 }, data: {} })
836
786
  })
837
787
 
838
788
  // Should batch into fewer updates (initial + batch result)
@@ -840,73 +790,73 @@ describe('createFlow', () => {
840
790
  })
841
791
  })
842
792
 
843
- describe('real-world patterns', () => {
844
- it('pipeline workflow', () => {
793
+ describe("real-world patterns", () => {
794
+ it("pipeline workflow", () => {
845
795
  const flow = createFlow({
846
796
  nodes: [
847
797
  {
848
- id: 'fetch',
849
- type: 'input',
798
+ id: "fetch",
799
+ type: "input",
850
800
  position: { x: 0, y: 0 },
851
- data: { label: 'Fetch Data' },
801
+ data: { label: "Fetch Data" },
852
802
  },
853
803
  {
854
- id: 'transform',
855
- type: 'process',
804
+ id: "transform",
805
+ type: "process",
856
806
  position: { x: 200, y: 0 },
857
- data: { label: 'Transform' },
807
+ data: { label: "Transform" },
858
808
  },
859
809
  {
860
- id: 'validate',
861
- type: 'process',
810
+ id: "validate",
811
+ type: "process",
862
812
  position: { x: 400, y: 0 },
863
- data: { label: 'Validate' },
813
+ data: { label: "Validate" },
864
814
  },
865
815
  {
866
- id: 'store',
867
- type: 'output',
816
+ id: "store",
817
+ type: "output",
868
818
  position: { x: 600, y: 0 },
869
- data: { label: 'Store' },
819
+ data: { label: "Store" },
870
820
  },
871
821
  ],
872
822
  edges: [
873
- { source: 'fetch', target: 'transform' },
874
- { source: 'transform', target: 'validate' },
875
- { source: 'validate', target: 'store' },
823
+ { source: "fetch", target: "transform" },
824
+ { source: "transform", target: "validate" },
825
+ { source: "validate", target: "store" },
876
826
  ],
877
827
  })
878
828
 
879
829
  expect(flow.nodes()).toHaveLength(4)
880
830
  expect(flow.edges()).toHaveLength(3)
881
- expect(flow.getOutgoers('fetch').map((n) => n.id)).toEqual(['transform'])
882
- expect(flow.getIncomers('store').map((n) => n.id)).toEqual(['validate'])
831
+ expect(flow.getOutgoers("fetch").map((n) => n.id)).toEqual(["transform"])
832
+ expect(flow.getIncomers("store").map((n) => n.id)).toEqual(["validate"])
883
833
  })
884
834
 
885
- it('branching workflow', () => {
835
+ it("branching workflow", () => {
886
836
  const flow = createFlow({
887
837
  nodes: [
888
- { id: 'start', position: { x: 0, y: 100 }, data: {} },
889
- { id: 'branch-a', position: { x: 200, y: 0 }, data: {} },
890
- { id: 'branch-b', position: { x: 200, y: 200 }, data: {} },
891
- { id: 'merge', position: { x: 400, y: 100 }, data: {} },
838
+ { id: "start", position: { x: 0, y: 100 }, data: {} },
839
+ { id: "branch-a", position: { x: 200, y: 0 }, data: {} },
840
+ { id: "branch-b", position: { x: 200, y: 200 }, data: {} },
841
+ { id: "merge", position: { x: 400, y: 100 }, data: {} },
892
842
  ],
893
843
  edges: [
894
- { source: 'start', target: 'branch-a' },
895
- { source: 'start', target: 'branch-b' },
896
- { source: 'branch-a', target: 'merge' },
897
- { source: 'branch-b', target: 'merge' },
844
+ { source: "start", target: "branch-a" },
845
+ { source: "start", target: "branch-b" },
846
+ { source: "branch-a", target: "merge" },
847
+ { source: "branch-b", target: "merge" },
898
848
  ],
899
849
  })
900
850
 
901
- expect(flow.getOutgoers('start')).toHaveLength(2)
902
- expect(flow.getIncomers('merge')).toHaveLength(2)
851
+ expect(flow.getOutgoers("start")).toHaveLength(2)
852
+ expect(flow.getIncomers("merge")).toHaveLength(2)
903
853
  })
904
854
  })
905
855
 
906
856
  // ─── Waypoints ─────────────────────────────────────────────────────────
907
857
 
908
- describe('edge waypoints', () => {
909
- it('getWaypointPath generates path through waypoints', () => {
858
+ describe("edge waypoints", () => {
859
+ it("getWaypointPath generates path through waypoints", () => {
910
860
  const result = getWaypointPath({
911
861
  sourceX: 0,
912
862
  sourceY: 0,
@@ -917,13 +867,13 @@ describe('createFlow', () => {
917
867
  { x: 200, y: -50 },
918
868
  ],
919
869
  })
920
- expect(result.path).toBe('M0,0 L100,50 L200,-50 L300,0')
870
+ expect(result.path).toBe("M0,0 L100,50 L200,-50 L300,0")
921
871
  // Label at middle waypoint (index 1 of 2)
922
872
  expect(result.labelX).toBe(200)
923
873
  expect(result.labelY).toBe(-50)
924
874
  })
925
875
 
926
- it('getWaypointPath with empty waypoints falls back to straight', () => {
876
+ it("getWaypointPath with empty waypoints falls back to straight", () => {
927
877
  const result = getWaypointPath({
928
878
  sourceX: 0,
929
879
  sourceY: 0,
@@ -931,33 +881,33 @@ describe('createFlow', () => {
931
881
  targetY: 100,
932
882
  waypoints: [],
933
883
  })
934
- expect(result.path).toBe('M0,0 L100,100')
884
+ expect(result.path).toBe("M0,0 L100,100")
935
885
  })
936
886
 
937
- it('addEdgeWaypoint adds a bend point', () => {
887
+ it("addEdgeWaypoint adds a bend point", () => {
938
888
  const flow = createFlow({
939
889
  nodes: [
940
- { id: '1', position: { x: 0, y: 0 }, data: {} },
941
- { id: '2', position: { x: 200, y: 0 }, data: {} },
890
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
891
+ { id: "2", position: { x: 200, y: 0 }, data: {} },
942
892
  ],
943
- edges: [{ id: 'e1', source: '1', target: '2' }],
893
+ edges: [{ id: "e1", source: "1", target: "2" }],
944
894
  })
945
895
 
946
- flow.addEdgeWaypoint('e1', { x: 100, y: 50 })
947
- expect(flow.getEdge('e1')!.waypoints).toEqual([{ x: 100, y: 50 }])
896
+ flow.addEdgeWaypoint("e1", { x: 100, y: 50 })
897
+ expect(flow.getEdge("e1")!.waypoints).toEqual([{ x: 100, y: 50 }])
948
898
  })
949
899
 
950
- it('addEdgeWaypoint at specific index', () => {
900
+ it("addEdgeWaypoint at specific index", () => {
951
901
  const flow = createFlow({
952
902
  nodes: [
953
- { id: '1', position: { x: 0, y: 0 }, data: {} },
954
- { id: '2', position: { x: 200, y: 0 }, data: {} },
903
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
904
+ { id: "2", position: { x: 200, y: 0 }, data: {} },
955
905
  ],
956
906
  edges: [
957
907
  {
958
- id: 'e1',
959
- source: '1',
960
- target: '2',
908
+ id: "e1",
909
+ source: "1",
910
+ target: "2",
961
911
  waypoints: [
962
912
  { x: 50, y: 0 },
963
913
  { x: 150, y: 0 },
@@ -966,108 +916,108 @@ describe('createFlow', () => {
966
916
  ],
967
917
  })
968
918
 
969
- flow.addEdgeWaypoint('e1', { x: 100, y: 50 }, 1)
970
- expect(flow.getEdge('e1')!.waypoints).toHaveLength(3)
971
- expect(flow.getEdge('e1')!.waypoints![1]).toEqual({ x: 100, y: 50 })
919
+ flow.addEdgeWaypoint("e1", { x: 100, y: 50 }, 1)
920
+ expect(flow.getEdge("e1")!.waypoints).toHaveLength(3)
921
+ expect(flow.getEdge("e1")!.waypoints![1]).toEqual({ x: 100, y: 50 })
972
922
  })
973
923
 
974
- it('removeEdgeWaypoint removes a bend point', () => {
924
+ it("removeEdgeWaypoint removes a bend point", () => {
975
925
  const flow = createFlow({
976
926
  nodes: [
977
- { id: '1', position: { x: 0, y: 0 }, data: {} },
978
- { id: '2', position: { x: 200, y: 0 }, data: {} },
927
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
928
+ { id: "2", position: { x: 200, y: 0 }, data: {} },
979
929
  ],
980
930
  edges: [
981
931
  {
982
- id: 'e1',
983
- source: '1',
984
- target: '2',
932
+ id: "e1",
933
+ source: "1",
934
+ target: "2",
985
935
  waypoints: [{ x: 100, y: 50 }],
986
936
  },
987
937
  ],
988
938
  })
989
939
 
990
- flow.removeEdgeWaypoint('e1', 0)
991
- expect(flow.getEdge('e1')!.waypoints).toBeUndefined()
940
+ flow.removeEdgeWaypoint("e1", 0)
941
+ expect(flow.getEdge("e1")!.waypoints).toBeUndefined()
992
942
  })
993
943
 
994
- it('updateEdgeWaypoint moves a bend point', () => {
944
+ it("updateEdgeWaypoint moves a bend point", () => {
995
945
  const flow = createFlow({
996
946
  nodes: [
997
- { id: '1', position: { x: 0, y: 0 }, data: {} },
998
- { id: '2', position: { x: 200, y: 0 }, data: {} },
947
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
948
+ { id: "2", position: { x: 200, y: 0 }, data: {} },
999
949
  ],
1000
950
  edges: [
1001
951
  {
1002
- id: 'e1',
1003
- source: '1',
1004
- target: '2',
952
+ id: "e1",
953
+ source: "1",
954
+ target: "2",
1005
955
  waypoints: [{ x: 100, y: 50 }],
1006
956
  },
1007
957
  ],
1008
958
  })
1009
959
 
1010
- flow.updateEdgeWaypoint('e1', 0, { x: 100, y: -50 })
1011
- expect(flow.getEdge('e1')!.waypoints![0]).toEqual({ x: 100, y: -50 })
960
+ flow.updateEdgeWaypoint("e1", 0, { x: 100, y: -50 })
961
+ expect(flow.getEdge("e1")!.waypoints![0]).toEqual({ x: 100, y: -50 })
1012
962
  })
1013
963
  })
1014
964
 
1015
965
  // ─── Search / Filter ───────────────────────────────────────────────────
1016
966
 
1017
- describe('search and filter', () => {
1018
- it('findNodes with predicate', () => {
967
+ describe("search and filter", () => {
968
+ it("findNodes with predicate", () => {
1019
969
  const flow = createFlow({
1020
970
  nodes: [
1021
- { id: '1', type: 'input', position: { x: 0, y: 0 }, data: {} },
1022
- { id: '2', type: 'process', position: { x: 100, y: 0 }, data: {} },
1023
- { id: '3', type: 'process', position: { x: 200, y: 0 }, data: {} },
1024
- { id: '4', type: 'output', position: { x: 300, y: 0 }, data: {} },
971
+ { id: "1", type: "input", position: { x: 0, y: 0 }, data: {} },
972
+ { id: "2", type: "process", position: { x: 100, y: 0 }, data: {} },
973
+ { id: "3", type: "process", position: { x: 200, y: 0 }, data: {} },
974
+ { id: "4", type: "output", position: { x: 300, y: 0 }, data: {} },
1025
975
  ],
1026
976
  })
1027
977
 
1028
- expect(flow.findNodes((n) => n.type === 'process')).toHaveLength(2)
1029
- expect(flow.findNodes((n) => n.type === 'input')).toHaveLength(1)
1030
- expect(flow.findNodes((n) => n.type === 'missing')).toHaveLength(0)
978
+ expect(flow.findNodes((n) => n.type === "process")).toHaveLength(2)
979
+ expect(flow.findNodes((n) => n.type === "input")).toHaveLength(1)
980
+ expect(flow.findNodes((n) => n.type === "missing")).toHaveLength(0)
1031
981
  })
1032
982
 
1033
- it('searchNodes by label', () => {
983
+ it("searchNodes by label", () => {
1034
984
  const flow = createFlow({
1035
985
  nodes: [
1036
- { id: '1', position: { x: 0, y: 0 }, data: { label: 'Fetch Data' } },
1037
- { id: '2', position: { x: 100, y: 0 }, data: { label: 'Transform' } },
986
+ { id: "1", position: { x: 0, y: 0 }, data: { label: "Fetch Data" } },
987
+ { id: "2", position: { x: 100, y: 0 }, data: { label: "Transform" } },
1038
988
  {
1039
- id: '3',
989
+ id: "3",
1040
990
  position: { x: 200, y: 0 },
1041
- data: { label: 'Fetch Users' },
991
+ data: { label: "Fetch Users" },
1042
992
  },
1043
993
  ],
1044
994
  })
1045
995
 
1046
- expect(flow.searchNodes('fetch')).toHaveLength(2)
1047
- expect(flow.searchNodes('transform')).toHaveLength(1)
1048
- expect(flow.searchNodes('FETCH')).toHaveLength(2) // case-insensitive
1049
- expect(flow.searchNodes('missing')).toHaveLength(0)
996
+ expect(flow.searchNodes("fetch")).toHaveLength(2)
997
+ expect(flow.searchNodes("transform")).toHaveLength(1)
998
+ expect(flow.searchNodes("FETCH")).toHaveLength(2) // case-insensitive
999
+ expect(flow.searchNodes("missing")).toHaveLength(0)
1050
1000
  })
1051
1001
 
1052
- it('searchNodes falls back to node id', () => {
1002
+ it("searchNodes falls back to node id", () => {
1053
1003
  const flow = createFlow({
1054
- nodes: [{ id: 'api-gateway', position: { x: 0, y: 0 }, data: {} }],
1004
+ nodes: [{ id: "api-gateway", position: { x: 0, y: 0 }, data: {} }],
1055
1005
  })
1056
1006
 
1057
- expect(flow.searchNodes('gateway')).toHaveLength(1)
1007
+ expect(flow.searchNodes("gateway")).toHaveLength(1)
1058
1008
  })
1059
1009
  })
1060
1010
 
1061
1011
  // ─── Export / Import ───────────────────────────────────────────────────
1062
1012
 
1063
- describe('toJSON / fromJSON', () => {
1064
- it('exports and imports flow state', () => {
1013
+ describe("toJSON / fromJSON", () => {
1014
+ it("exports and imports flow state", () => {
1065
1015
  const flow = createFlow({
1066
1016
  nodes: [
1067
- { id: '1', position: { x: 0, y: 0 }, data: { label: 'A' } },
1068
- { id: '2', position: { x: 200, y: 0 }, data: { label: 'B' } },
1017
+ { id: "1", position: { x: 0, y: 0 }, data: { label: "A" } },
1018
+ { id: "2", position: { x: 200, y: 0 }, data: { label: "B" } },
1069
1019
  ],
1070
- edges: [{ source: '1', target: '2' }],
1020
+ edges: [{ source: "1", target: "2" }],
1071
1021
  })
1072
1022
 
1073
1023
  flow.zoomTo(1.5)
@@ -1086,12 +1036,12 @@ describe('createFlow', () => {
1086
1036
  expect(flow2.zoom()).toBe(1.5)
1087
1037
  })
1088
1038
 
1089
- it('fromJSON without viewport keeps current', () => {
1039
+ it("fromJSON without viewport keeps current", () => {
1090
1040
  const flow = createFlow()
1091
1041
  flow.zoomTo(2)
1092
1042
 
1093
1043
  flow.fromJSON({
1094
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1044
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1095
1045
  edges: [],
1096
1046
  })
1097
1047
 
@@ -1102,26 +1052,26 @@ describe('createFlow', () => {
1102
1052
 
1103
1053
  // ─── Collision detection ───────────────────────────────────────────────
1104
1054
 
1105
- describe('collision detection', () => {
1106
- it('getOverlappingNodes detects overlap', () => {
1055
+ describe("collision detection", () => {
1056
+ it("getOverlappingNodes detects overlap", () => {
1107
1057
  const flow = createFlow({
1108
1058
  nodes: [
1109
1059
  {
1110
- id: '1',
1060
+ id: "1",
1111
1061
  position: { x: 0, y: 0 },
1112
1062
  width: 100,
1113
1063
  height: 50,
1114
1064
  data: {},
1115
1065
  },
1116
1066
  {
1117
- id: '2',
1067
+ id: "2",
1118
1068
  position: { x: 50, y: 25 },
1119
1069
  width: 100,
1120
1070
  height: 50,
1121
1071
  data: {},
1122
1072
  },
1123
1073
  {
1124
- id: '3',
1074
+ id: "3",
1125
1075
  position: { x: 500, y: 500 },
1126
1076
  width: 100,
1127
1077
  height: 50,
@@ -1130,116 +1080,116 @@ describe('createFlow', () => {
1130
1080
  ],
1131
1081
  })
1132
1082
 
1133
- expect(flow.getOverlappingNodes('1')).toHaveLength(1)
1134
- expect(flow.getOverlappingNodes('1')[0]!.id).toBe('2')
1135
- expect(flow.getOverlappingNodes('3')).toHaveLength(0)
1083
+ expect(flow.getOverlappingNodes("1")).toHaveLength(1)
1084
+ expect(flow.getOverlappingNodes("1")[0]!.id).toBe("2")
1085
+ expect(flow.getOverlappingNodes("3")).toHaveLength(0)
1136
1086
  })
1137
1087
  })
1138
1088
 
1139
1089
  // ─── Proximity connect ─────────────────────────────────────────────────
1140
1090
 
1141
- describe('proximity connect', () => {
1142
- it('finds nearest unconnected node', () => {
1091
+ describe("proximity connect", () => {
1092
+ it("finds nearest unconnected node", () => {
1143
1093
  const flow = createFlow({
1144
1094
  nodes: [
1145
- { id: '1', position: { x: 0, y: 0 }, data: {} },
1146
- { id: '2', position: { x: 100, y: 0 }, data: {} },
1147
- { id: '3', position: { x: 500, y: 500 }, data: {} },
1095
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
1096
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
1097
+ { id: "3", position: { x: 500, y: 500 }, data: {} },
1148
1098
  ],
1149
1099
  })
1150
1100
 
1151
- const conn = flow.getProximityConnection('1', 200)
1101
+ const conn = flow.getProximityConnection("1", 200)
1152
1102
  expect(conn).not.toBeNull()
1153
- expect(conn!.target).toBe('2')
1103
+ expect(conn!.target).toBe("2")
1154
1104
  })
1155
1105
 
1156
- it('returns null when no node is close', () => {
1106
+ it("returns null when no node is close", () => {
1157
1107
  const flow = createFlow({
1158
1108
  nodes: [
1159
- { id: '1', position: { x: 0, y: 0 }, data: {} },
1160
- { id: '2', position: { x: 500, y: 500 }, data: {} },
1109
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
1110
+ { id: "2", position: { x: 500, y: 500 }, data: {} },
1161
1111
  ],
1162
1112
  })
1163
1113
 
1164
- expect(flow.getProximityConnection('1', 50)).toBeNull()
1114
+ expect(flow.getProximityConnection("1", 50)).toBeNull()
1165
1115
  })
1166
1116
 
1167
- it('skips already connected nodes', () => {
1117
+ it("skips already connected nodes", () => {
1168
1118
  const flow = createFlow({
1169
1119
  nodes: [
1170
- { id: '1', position: { x: 0, y: 0 }, data: {} },
1171
- { id: '2', position: { x: 100, y: 0 }, data: {} },
1120
+ { id: "1", position: { x: 0, y: 0 }, data: {} },
1121
+ { id: "2", position: { x: 100, y: 0 }, data: {} },
1172
1122
  ],
1173
- edges: [{ source: '1', target: '2' }],
1123
+ edges: [{ source: "1", target: "2" }],
1174
1124
  })
1175
1125
 
1176
- expect(flow.getProximityConnection('1', 200)).toBeNull()
1126
+ expect(flow.getProximityConnection("1", 200)).toBeNull()
1177
1127
  })
1178
1128
  })
1179
1129
 
1180
1130
  // ─── Node extent ───────────────────────────────────────────────────────
1181
1131
 
1182
- describe('node extent', () => {
1183
- it('clamps node position to extent', () => {
1132
+ describe("node extent", () => {
1133
+ it("clamps node position to extent", () => {
1184
1134
  const flow = createFlow({
1185
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1135
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1186
1136
  nodeExtent: [
1187
1137
  [0, 0],
1188
1138
  [500, 500],
1189
1139
  ],
1190
1140
  })
1191
1141
 
1192
- flow.updateNodePosition('1', { x: -100, y: -100 })
1193
- expect(flow.getNode('1')!.position.x).toBe(0)
1194
- expect(flow.getNode('1')!.position.y).toBe(0)
1142
+ flow.updateNodePosition("1", { x: -100, y: -100 })
1143
+ expect(flow.getNode("1")!.position.x).toBe(0)
1144
+ expect(flow.getNode("1")!.position.y).toBe(0)
1195
1145
 
1196
- flow.updateNodePosition('1', { x: 600, y: 600 })
1197
- expect(flow.getNode('1')!.position.x).toBeLessThanOrEqual(500)
1198
- expect(flow.getNode('1')!.position.y).toBeLessThanOrEqual(500)
1146
+ flow.updateNodePosition("1", { x: 600, y: 600 })
1147
+ expect(flow.getNode("1")!.position.x).toBeLessThanOrEqual(500)
1148
+ expect(flow.getNode("1")!.position.y).toBeLessThanOrEqual(500)
1199
1149
  })
1200
1150
 
1201
- it('setNodeExtent changes boundaries dynamically', () => {
1151
+ it("setNodeExtent changes boundaries dynamically", () => {
1202
1152
  const flow = createFlow({
1203
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1153
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1204
1154
  })
1205
1155
 
1206
1156
  // No extent — no clamping
1207
- flow.updateNodePosition('1', { x: -999, y: -999 })
1208
- expect(flow.getNode('1')!.position.x).toBe(-999)
1157
+ flow.updateNodePosition("1", { x: -999, y: -999 })
1158
+ expect(flow.getNode("1")!.position.x).toBe(-999)
1209
1159
 
1210
1160
  // Set extent — large enough for default node size (150x40)
1211
1161
  flow.setNodeExtent([
1212
1162
  [0, 0],
1213
1163
  [500, 500],
1214
1164
  ])
1215
- flow.updateNodePosition('1', { x: -999, y: -999 })
1216
- expect(flow.getNode('1')!.position.x).toBe(0)
1165
+ flow.updateNodePosition("1", { x: -999, y: -999 })
1166
+ expect(flow.getNode("1")!.position.x).toBe(0)
1217
1167
  })
1218
1168
  })
1219
1169
 
1220
1170
  // ─── Undo / Redo ───────────────────────────────────────────────────────
1221
1171
 
1222
- describe('undo / redo', () => {
1223
- it('undo restores previous state', () => {
1172
+ describe("undo / redo", () => {
1173
+ it("undo restores previous state", () => {
1224
1174
  const flow = createFlow({
1225
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1175
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1226
1176
  })
1227
1177
 
1228
1178
  flow.pushHistory()
1229
- flow.addNode({ id: '2', position: { x: 100, y: 0 }, data: {} })
1179
+ flow.addNode({ id: "2", position: { x: 100, y: 0 }, data: {} })
1230
1180
  expect(flow.nodes()).toHaveLength(2)
1231
1181
 
1232
1182
  flow.undo()
1233
1183
  expect(flow.nodes()).toHaveLength(1)
1234
1184
  })
1235
1185
 
1236
- it('redo restores undone state', () => {
1186
+ it("redo restores undone state", () => {
1237
1187
  const flow = createFlow({
1238
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1188
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1239
1189
  })
1240
1190
 
1241
1191
  flow.pushHistory()
1242
- flow.addNode({ id: '2', position: { x: 100, y: 0 }, data: {} })
1192
+ flow.addNode({ id: "2", position: { x: 100, y: 0 }, data: {} })
1243
1193
 
1244
1194
  flow.undo()
1245
1195
  expect(flow.nodes()).toHaveLength(1)
@@ -1251,18 +1201,18 @@ describe('createFlow', () => {
1251
1201
 
1252
1202
  // ─── Copy / Paste ──────────────────────────────────────────────────────
1253
1203
 
1254
- describe('copy / paste', () => {
1255
- it('copies and pastes selected nodes', () => {
1204
+ describe("copy / paste", () => {
1205
+ it("copies and pastes selected nodes", () => {
1256
1206
  const flow = createFlow({
1257
1207
  nodes: [
1258
- { id: '1', position: { x: 0, y: 0 }, data: { label: 'A' } },
1259
- { id: '2', position: { x: 200, y: 0 }, data: { label: 'B' } },
1208
+ { id: "1", position: { x: 0, y: 0 }, data: { label: "A" } },
1209
+ { id: "2", position: { x: 200, y: 0 }, data: { label: "B" } },
1260
1210
  ],
1261
- edges: [{ source: '1', target: '2' }],
1211
+ edges: [{ source: "1", target: "2" }],
1262
1212
  })
1263
1213
 
1264
- flow.selectNode('1')
1265
- flow.selectNode('2', true)
1214
+ flow.selectNode("1")
1215
+ flow.selectNode("2", true)
1266
1216
  flow.copySelected()
1267
1217
  flow.paste()
1268
1218
 
@@ -1272,9 +1222,9 @@ describe('createFlow', () => {
1272
1222
  expect(new Set(ids).size).toBe(4)
1273
1223
  })
1274
1224
 
1275
- it('paste without copy does nothing', () => {
1225
+ it("paste without copy does nothing", () => {
1276
1226
  const flow = createFlow({
1277
- nodes: [{ id: '1', position: { x: 0, y: 0 }, data: {} }],
1227
+ nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
1278
1228
  })
1279
1229
 
1280
1230
  flow.paste()