@pyreon/flow 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/analysis/index.js.html +1 -1
- package/lib/{elk.bundled-B9dPTHTZ.js → elk.bundled-wgxWJMYd.js} +2 -2
- package/lib/elk.bundled-wgxWJMYd.js.map +1 -0
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +13 -13
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +14 -7
- package/src/components/background.tsx +9 -14
- package/src/components/controls.tsx +11 -18
- package/src/components/flow-component.tsx +57 -109
- package/src/components/handle.tsx +8 -8
- package/src/components/minimap.tsx +10 -27
- package/src/components/node-resizer.tsx +25 -25
- package/src/components/node-toolbar.tsx +12 -21
- package/src/components/panel.tsx +9 -9
- package/src/edges.ts +16 -47
- package/src/flow.ts +32 -88
- package/src/index.ts +17 -17
- package/src/layout.ts +23 -31
- package/src/tests/flow-advanced.test.ts +191 -200
- package/src/tests/flow.test.ts +418 -468
- package/src/types.ts +28 -42
- package/lib/elk.bundled-B9dPTHTZ.js.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
2
|
import {
|
|
3
3
|
getBezierPath,
|
|
4
4
|
getSmartHandlePositions,
|
|
5
5
|
getSmoothStepPath,
|
|
6
6
|
getWaypointPath,
|
|
7
|
-
} from
|
|
8
|
-
import { createFlow } from
|
|
9
|
-
import { Position } from
|
|
7
|
+
} from "../edges"
|
|
8
|
+
import { createFlow } from "../flow"
|
|
9
|
+
import { Position } from "../types"
|
|
10
10
|
|
|
11
11
|
// ─── Edge paths — additional coverage ────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
describe(
|
|
14
|
-
it(
|
|
13
|
+
describe("edge paths — additional branches", () => {
|
|
14
|
+
it("getBezierPath with Top/Left source positions", () => {
|
|
15
15
|
const top = getBezierPath({
|
|
16
16
|
sourceX: 100,
|
|
17
17
|
sourceY: 100,
|
|
@@ -33,13 +33,8 @@ describe('edge paths — additional branches', () => {
|
|
|
33
33
|
expect(left.path).toMatch(/^M/)
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
it(
|
|
37
|
-
for (const pos of [
|
|
38
|
-
Position.Top,
|
|
39
|
-
Position.Right,
|
|
40
|
-
Position.Bottom,
|
|
41
|
-
Position.Left,
|
|
42
|
-
]) {
|
|
36
|
+
it("getBezierPath with all target positions", () => {
|
|
37
|
+
for (const pos of [Position.Top, Position.Right, Position.Bottom, Position.Left]) {
|
|
43
38
|
const result = getBezierPath({
|
|
44
39
|
sourceX: 0,
|
|
45
40
|
sourceY: 0,
|
|
@@ -52,7 +47,7 @@ describe('edge paths — additional branches', () => {
|
|
|
52
47
|
}
|
|
53
48
|
})
|
|
54
49
|
|
|
55
|
-
it(
|
|
50
|
+
it("getSmoothStepPath with horizontal→horizontal positions", () => {
|
|
56
51
|
const result = getSmoothStepPath({
|
|
57
52
|
sourceX: 0,
|
|
58
53
|
sourceY: 0,
|
|
@@ -64,7 +59,7 @@ describe('edge paths — additional branches', () => {
|
|
|
64
59
|
expect(result.path).toMatch(/^M/)
|
|
65
60
|
})
|
|
66
61
|
|
|
67
|
-
it(
|
|
62
|
+
it("getSmoothStepPath with vertical→vertical positions", () => {
|
|
68
63
|
const result = getSmoothStepPath({
|
|
69
64
|
sourceX: 0,
|
|
70
65
|
sourceY: 0,
|
|
@@ -76,7 +71,7 @@ describe('edge paths — additional branches', () => {
|
|
|
76
71
|
expect(result.path).toMatch(/^M/)
|
|
77
72
|
})
|
|
78
73
|
|
|
79
|
-
it(
|
|
74
|
+
it("getSmoothStepPath with vertical→horizontal", () => {
|
|
80
75
|
const result = getSmoothStepPath({
|
|
81
76
|
sourceX: 0,
|
|
82
77
|
sourceY: 0,
|
|
@@ -88,7 +83,7 @@ describe('edge paths — additional branches', () => {
|
|
|
88
83
|
expect(result.path).toMatch(/^M/)
|
|
89
84
|
})
|
|
90
85
|
|
|
91
|
-
it(
|
|
86
|
+
it("getSmoothStepPath with Left source", () => {
|
|
92
87
|
const result = getSmoothStepPath({
|
|
93
88
|
sourceX: 200,
|
|
94
89
|
sourceY: 0,
|
|
@@ -100,7 +95,7 @@ describe('edge paths — additional branches', () => {
|
|
|
100
95
|
expect(result.path).toMatch(/^M/)
|
|
101
96
|
})
|
|
102
97
|
|
|
103
|
-
it(
|
|
98
|
+
it("getSmoothStepPath with Bottom target", () => {
|
|
104
99
|
const result = getSmoothStepPath({
|
|
105
100
|
sourceX: 0,
|
|
106
101
|
sourceY: 0,
|
|
@@ -112,47 +107,45 @@ describe('edge paths — additional branches', () => {
|
|
|
112
107
|
expect(result.path).toMatch(/^M/)
|
|
113
108
|
})
|
|
114
109
|
|
|
115
|
-
it(
|
|
110
|
+
it("getSmartHandlePositions with nodes at various positions", () => {
|
|
116
111
|
// Target to the left
|
|
117
112
|
const leftward = getSmartHandlePositions(
|
|
118
|
-
{ id:
|
|
119
|
-
{ id:
|
|
113
|
+
{ id: "1", position: { x: 200, y: 0 }, data: {} },
|
|
114
|
+
{ id: "2", position: { x: 0, y: 0 }, data: {} },
|
|
120
115
|
)
|
|
121
116
|
expect(leftward.sourcePosition).toBe(Position.Left)
|
|
122
117
|
expect(leftward.targetPosition).toBe(Position.Right)
|
|
123
118
|
|
|
124
119
|
// Target below
|
|
125
120
|
const downward = getSmartHandlePositions(
|
|
126
|
-
{ id:
|
|
127
|
-
{ id:
|
|
121
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
122
|
+
{ id: "2", position: { x: 0, y: 300 }, data: {} },
|
|
128
123
|
)
|
|
129
124
|
expect(downward.sourcePosition).toBe(Position.Bottom)
|
|
130
125
|
expect(downward.targetPosition).toBe(Position.Top)
|
|
131
126
|
|
|
132
127
|
// Target above
|
|
133
128
|
const upward = getSmartHandlePositions(
|
|
134
|
-
{ id:
|
|
135
|
-
{ id:
|
|
129
|
+
{ id: "1", position: { x: 0, y: 300 }, data: {} },
|
|
130
|
+
{ id: "2", position: { x: 0, y: 0 }, data: {} },
|
|
136
131
|
)
|
|
137
132
|
expect(upward.sourcePosition).toBe(Position.Top)
|
|
138
133
|
expect(upward.targetPosition).toBe(Position.Bottom)
|
|
139
134
|
})
|
|
140
135
|
|
|
141
|
-
it(
|
|
136
|
+
it("getSmartHandlePositions with configured handles", () => {
|
|
142
137
|
const result = getSmartHandlePositions(
|
|
143
138
|
{
|
|
144
|
-
id:
|
|
139
|
+
id: "1",
|
|
145
140
|
position: { x: 0, y: 0 },
|
|
146
141
|
data: {},
|
|
147
|
-
sourceHandles: [
|
|
148
|
-
{ type: 'source', position: Position.Bottom, id: 'out' },
|
|
149
|
-
],
|
|
142
|
+
sourceHandles: [{ type: "source", position: Position.Bottom, id: "out" }],
|
|
150
143
|
},
|
|
151
144
|
{
|
|
152
|
-
id:
|
|
145
|
+
id: "2",
|
|
153
146
|
position: { x: 200, y: 200 },
|
|
154
147
|
data: {},
|
|
155
|
-
targetHandles: [{ type:
|
|
148
|
+
targetHandles: [{ type: "target", position: Position.Top, id: "in" }],
|
|
156
149
|
},
|
|
157
150
|
)
|
|
158
151
|
// Should use configured handle positions
|
|
@@ -160,7 +153,7 @@ describe('edge paths — additional branches', () => {
|
|
|
160
153
|
expect(result.targetPosition).toBe(Position.Top)
|
|
161
154
|
})
|
|
162
155
|
|
|
163
|
-
it(
|
|
156
|
+
it("getWaypointPath with single waypoint", () => {
|
|
164
157
|
const result = getWaypointPath({
|
|
165
158
|
sourceX: 0,
|
|
166
159
|
sourceY: 0,
|
|
@@ -168,7 +161,7 @@ describe('edge paths — additional branches', () => {
|
|
|
168
161
|
targetY: 0,
|
|
169
162
|
waypoints: [{ x: 100, y: 50 }],
|
|
170
163
|
})
|
|
171
|
-
expect(result.path).toBe(
|
|
164
|
+
expect(result.path).toBe("M0,0 L100,50 L200,0")
|
|
172
165
|
expect(result.labelX).toBe(100)
|
|
173
166
|
expect(result.labelY).toBe(50)
|
|
174
167
|
})
|
|
@@ -176,20 +169,20 @@ describe('edge paths — additional branches', () => {
|
|
|
176
169
|
|
|
177
170
|
// ─── Flow — advanced operations ──────────────────────────────────────────────
|
|
178
171
|
|
|
179
|
-
describe(
|
|
180
|
-
describe(
|
|
181
|
-
it(
|
|
172
|
+
describe("createFlow — advanced", () => {
|
|
173
|
+
describe("resolveCollisions", () => {
|
|
174
|
+
it("pushes overlapping nodes apart", () => {
|
|
182
175
|
const flow = createFlow({
|
|
183
176
|
nodes: [
|
|
184
177
|
{
|
|
185
|
-
id:
|
|
178
|
+
id: "1",
|
|
186
179
|
position: { x: 0, y: 0 },
|
|
187
180
|
width: 100,
|
|
188
181
|
height: 50,
|
|
189
182
|
data: {},
|
|
190
183
|
},
|
|
191
184
|
{
|
|
192
|
-
id:
|
|
185
|
+
id: "2",
|
|
193
186
|
position: { x: 50, y: 0 },
|
|
194
187
|
width: 100,
|
|
195
188
|
height: 50,
|
|
@@ -198,25 +191,25 @@ describe('createFlow — advanced', () => {
|
|
|
198
191
|
],
|
|
199
192
|
})
|
|
200
193
|
|
|
201
|
-
flow.resolveCollisions(
|
|
194
|
+
flow.resolveCollisions("1")
|
|
202
195
|
// Node 2 should have moved (either X or Y)
|
|
203
|
-
const node2 = flow.getNode(
|
|
196
|
+
const node2 = flow.getNode("2")!
|
|
204
197
|
const moved = node2.position.x !== 50 || node2.position.y !== 0
|
|
205
198
|
expect(moved).toBe(true)
|
|
206
199
|
})
|
|
207
200
|
|
|
208
|
-
it(
|
|
201
|
+
it("does nothing when no overlaps", () => {
|
|
209
202
|
const flow = createFlow({
|
|
210
203
|
nodes: [
|
|
211
204
|
{
|
|
212
|
-
id:
|
|
205
|
+
id: "1",
|
|
213
206
|
position: { x: 0, y: 0 },
|
|
214
207
|
width: 100,
|
|
215
208
|
height: 50,
|
|
216
209
|
data: {},
|
|
217
210
|
},
|
|
218
211
|
{
|
|
219
|
-
id:
|
|
212
|
+
id: "2",
|
|
220
213
|
position: { x: 500, y: 500 },
|
|
221
214
|
width: 100,
|
|
222
215
|
height: 50,
|
|
@@ -225,22 +218,22 @@ describe('createFlow — advanced', () => {
|
|
|
225
218
|
],
|
|
226
219
|
})
|
|
227
220
|
|
|
228
|
-
flow.resolveCollisions(
|
|
229
|
-
expect(flow.getNode(
|
|
221
|
+
flow.resolveCollisions("1")
|
|
222
|
+
expect(flow.getNode("2")!.position).toEqual({ x: 500, y: 500 })
|
|
230
223
|
})
|
|
231
224
|
|
|
232
|
-
it(
|
|
225
|
+
it("resolves vertical overlaps", () => {
|
|
233
226
|
const flow = createFlow({
|
|
234
227
|
nodes: [
|
|
235
228
|
{
|
|
236
|
-
id:
|
|
229
|
+
id: "1",
|
|
237
230
|
position: { x: 0, y: 0 },
|
|
238
231
|
width: 200,
|
|
239
232
|
height: 50,
|
|
240
233
|
data: {},
|
|
241
234
|
},
|
|
242
235
|
{
|
|
243
|
-
id:
|
|
236
|
+
id: "2",
|
|
244
237
|
position: { x: 0, y: 30 },
|
|
245
238
|
width: 200,
|
|
246
239
|
height: 50,
|
|
@@ -249,123 +242,121 @@ describe('createFlow — advanced', () => {
|
|
|
249
242
|
],
|
|
250
243
|
})
|
|
251
244
|
|
|
252
|
-
flow.resolveCollisions(
|
|
253
|
-
const node2 = flow.getNode(
|
|
245
|
+
flow.resolveCollisions("1")
|
|
246
|
+
const node2 = flow.getNode("2")!
|
|
254
247
|
// Should push vertically since horizontal overlap is larger
|
|
255
248
|
expect(node2.position.y).not.toBe(30)
|
|
256
249
|
})
|
|
257
250
|
})
|
|
258
251
|
|
|
259
|
-
describe(
|
|
260
|
-
it(
|
|
252
|
+
describe("getChildNodes / getAbsolutePosition", () => {
|
|
253
|
+
it("returns child nodes of a group", () => {
|
|
261
254
|
const flow = createFlow({
|
|
262
255
|
nodes: [
|
|
263
256
|
{
|
|
264
|
-
id:
|
|
257
|
+
id: "group",
|
|
265
258
|
position: { x: 0, y: 0 },
|
|
266
259
|
group: true,
|
|
267
260
|
data: {},
|
|
268
261
|
},
|
|
269
262
|
{
|
|
270
|
-
id:
|
|
263
|
+
id: "child1",
|
|
271
264
|
position: { x: 10, y: 10 },
|
|
272
|
-
parentId:
|
|
265
|
+
parentId: "group",
|
|
273
266
|
data: {},
|
|
274
267
|
},
|
|
275
268
|
{
|
|
276
|
-
id:
|
|
269
|
+
id: "child2",
|
|
277
270
|
position: { x: 20, y: 20 },
|
|
278
|
-
parentId:
|
|
271
|
+
parentId: "group",
|
|
279
272
|
data: {},
|
|
280
273
|
},
|
|
281
|
-
{ id:
|
|
274
|
+
{ id: "other", position: { x: 100, y: 100 }, data: {} },
|
|
282
275
|
],
|
|
283
276
|
})
|
|
284
277
|
|
|
285
|
-
const children = flow.getChildNodes(
|
|
278
|
+
const children = flow.getChildNodes("group")
|
|
286
279
|
expect(children).toHaveLength(2)
|
|
287
|
-
expect(children.map((n) => n.id)).toEqual(
|
|
288
|
-
expect.arrayContaining(['child1', 'child2']),
|
|
289
|
-
)
|
|
280
|
+
expect(children.map((n) => n.id)).toEqual(expect.arrayContaining(["child1", "child2"]))
|
|
290
281
|
})
|
|
291
282
|
|
|
292
|
-
it(
|
|
283
|
+
it("getAbsolutePosition accounts for parent offset", () => {
|
|
293
284
|
const flow = createFlow({
|
|
294
285
|
nodes: [
|
|
295
286
|
{
|
|
296
|
-
id:
|
|
287
|
+
id: "parent",
|
|
297
288
|
position: { x: 100, y: 200 },
|
|
298
289
|
data: {},
|
|
299
290
|
},
|
|
300
291
|
{
|
|
301
|
-
id:
|
|
292
|
+
id: "child",
|
|
302
293
|
position: { x: 10, y: 20 },
|
|
303
|
-
parentId:
|
|
294
|
+
parentId: "parent",
|
|
304
295
|
data: {},
|
|
305
296
|
},
|
|
306
297
|
],
|
|
307
298
|
})
|
|
308
299
|
|
|
309
|
-
const abs = flow.getAbsolutePosition(
|
|
300
|
+
const abs = flow.getAbsolutePosition("child")
|
|
310
301
|
expect(abs).toEqual({ x: 110, y: 220 })
|
|
311
302
|
})
|
|
312
303
|
|
|
313
|
-
it(
|
|
304
|
+
it("getAbsolutePosition for root node returns position", () => {
|
|
314
305
|
const flow = createFlow({
|
|
315
|
-
nodes: [{ id:
|
|
306
|
+
nodes: [{ id: "1", position: { x: 50, y: 75 }, data: {} }],
|
|
316
307
|
})
|
|
317
308
|
|
|
318
|
-
expect(flow.getAbsolutePosition(
|
|
309
|
+
expect(flow.getAbsolutePosition("1")).toEqual({ x: 50, y: 75 })
|
|
319
310
|
})
|
|
320
311
|
|
|
321
|
-
it(
|
|
312
|
+
it("getAbsolutePosition for missing node returns 0,0", () => {
|
|
322
313
|
const flow = createFlow()
|
|
323
|
-
expect(flow.getAbsolutePosition(
|
|
314
|
+
expect(flow.getAbsolutePosition("missing")).toEqual({ x: 0, y: 0 })
|
|
324
315
|
})
|
|
325
316
|
})
|
|
326
317
|
|
|
327
|
-
describe(
|
|
328
|
-
it(
|
|
318
|
+
describe("moveSelectedNodes", () => {
|
|
319
|
+
it("moves all selected nodes by delta", () => {
|
|
329
320
|
const flow = createFlow({
|
|
330
321
|
nodes: [
|
|
331
|
-
{ id:
|
|
332
|
-
{ id:
|
|
333
|
-
{ id:
|
|
322
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
323
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
324
|
+
{ id: "3", position: { x: 200, y: 0 }, data: {} },
|
|
334
325
|
],
|
|
335
326
|
})
|
|
336
327
|
|
|
337
|
-
flow.selectNode(
|
|
338
|
-
flow.selectNode(
|
|
328
|
+
flow.selectNode("1")
|
|
329
|
+
flow.selectNode("2", true)
|
|
339
330
|
flow.moveSelectedNodes(50, 25)
|
|
340
331
|
|
|
341
|
-
expect(flow.getNode(
|
|
342
|
-
expect(flow.getNode(
|
|
343
|
-
expect(flow.getNode(
|
|
332
|
+
expect(flow.getNode("1")!.position).toEqual({ x: 50, y: 25 })
|
|
333
|
+
expect(flow.getNode("2")!.position).toEqual({ x: 150, y: 25 })
|
|
334
|
+
expect(flow.getNode("3")!.position).toEqual({ x: 200, y: 0 }) // not selected
|
|
344
335
|
})
|
|
345
336
|
|
|
346
|
-
it(
|
|
337
|
+
it("does nothing with no selection", () => {
|
|
347
338
|
const flow = createFlow({
|
|
348
|
-
nodes: [{ id:
|
|
339
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
349
340
|
})
|
|
350
341
|
|
|
351
342
|
flow.moveSelectedNodes(100, 100)
|
|
352
|
-
expect(flow.getNode(
|
|
343
|
+
expect(flow.getNode("1")!.position).toEqual({ x: 0, y: 0 })
|
|
353
344
|
})
|
|
354
345
|
})
|
|
355
346
|
|
|
356
|
-
describe(
|
|
357
|
-
it(
|
|
347
|
+
describe("getSnapLines", () => {
|
|
348
|
+
it("snaps to aligned nodes", () => {
|
|
358
349
|
const flow = createFlow({
|
|
359
350
|
nodes: [
|
|
360
351
|
{
|
|
361
|
-
id:
|
|
352
|
+
id: "1",
|
|
362
353
|
position: { x: 100, y: 100 },
|
|
363
354
|
width: 100,
|
|
364
355
|
height: 50,
|
|
365
356
|
data: {},
|
|
366
357
|
},
|
|
367
358
|
{
|
|
368
|
-
id:
|
|
359
|
+
id: "2",
|
|
369
360
|
position: { x: 300, y: 200 },
|
|
370
361
|
width: 100,
|
|
371
362
|
height: 50,
|
|
@@ -375,23 +366,23 @@ describe('createFlow — advanced', () => {
|
|
|
375
366
|
})
|
|
376
367
|
|
|
377
368
|
// Move node 2 close to node 1's center X
|
|
378
|
-
const snap = flow.getSnapLines(
|
|
369
|
+
const snap = flow.getSnapLines("2", { x: 98, y: 200 })
|
|
379
370
|
expect(snap.x).not.toBeNull()
|
|
380
371
|
expect(snap.snappedPosition.x).not.toBe(98)
|
|
381
372
|
})
|
|
382
373
|
|
|
383
|
-
it(
|
|
374
|
+
it("returns null lines when no alignment", () => {
|
|
384
375
|
const flow = createFlow({
|
|
385
376
|
nodes: [
|
|
386
377
|
{
|
|
387
|
-
id:
|
|
378
|
+
id: "1",
|
|
388
379
|
position: { x: 0, y: 0 },
|
|
389
380
|
width: 100,
|
|
390
381
|
height: 50,
|
|
391
382
|
data: {},
|
|
392
383
|
},
|
|
393
384
|
{
|
|
394
|
-
id:
|
|
385
|
+
id: "2",
|
|
395
386
|
position: { x: 500, y: 500 },
|
|
396
387
|
width: 100,
|
|
397
388
|
height: 50,
|
|
@@ -400,23 +391,23 @@ describe('createFlow — advanced', () => {
|
|
|
400
391
|
],
|
|
401
392
|
})
|
|
402
393
|
|
|
403
|
-
const snap = flow.getSnapLines(
|
|
394
|
+
const snap = flow.getSnapLines("2", { x: 500, y: 500 })
|
|
404
395
|
expect(snap.x).toBeNull()
|
|
405
396
|
expect(snap.y).toBeNull()
|
|
406
397
|
})
|
|
407
398
|
|
|
408
|
-
it(
|
|
399
|
+
it("snaps to left edge alignment", () => {
|
|
409
400
|
const flow = createFlow({
|
|
410
401
|
nodes: [
|
|
411
402
|
{
|
|
412
|
-
id:
|
|
403
|
+
id: "1",
|
|
413
404
|
position: { x: 100, y: 0 },
|
|
414
405
|
width: 100,
|
|
415
406
|
height: 50,
|
|
416
407
|
data: {},
|
|
417
408
|
},
|
|
418
409
|
{
|
|
419
|
-
id:
|
|
410
|
+
id: "2",
|
|
420
411
|
position: { x: 100, y: 100 },
|
|
421
412
|
width: 100,
|
|
422
413
|
height: 50,
|
|
@@ -426,22 +417,22 @@ describe('createFlow — advanced', () => {
|
|
|
426
417
|
})
|
|
427
418
|
|
|
428
419
|
// Node 2 is already aligned on left edge
|
|
429
|
-
const snap = flow.getSnapLines(
|
|
420
|
+
const snap = flow.getSnapLines("2", { x: 102, y: 100 })
|
|
430
421
|
expect(snap.snappedPosition.x).toBe(100) // snapped to left edge
|
|
431
422
|
})
|
|
432
423
|
|
|
433
|
-
it(
|
|
424
|
+
it("snaps to right edge alignment", () => {
|
|
434
425
|
const flow = createFlow({
|
|
435
426
|
nodes: [
|
|
436
427
|
{
|
|
437
|
-
id:
|
|
428
|
+
id: "1",
|
|
438
429
|
position: { x: 100, y: 0 },
|
|
439
430
|
width: 100,
|
|
440
431
|
height: 50,
|
|
441
432
|
data: {},
|
|
442
433
|
},
|
|
443
434
|
{
|
|
444
|
-
id:
|
|
435
|
+
id: "2",
|
|
445
436
|
position: { x: 100, y: 100 },
|
|
446
437
|
width: 100,
|
|
447
438
|
height: 50,
|
|
@@ -451,24 +442,24 @@ describe('createFlow — advanced', () => {
|
|
|
451
442
|
})
|
|
452
443
|
|
|
453
444
|
// Move node 2 so right edges almost align
|
|
454
|
-
const snap = flow.getSnapLines(
|
|
445
|
+
const snap = flow.getSnapLines("2", { x: 98, y: 100 })
|
|
455
446
|
// Right edge of node1: 200, right edge of moved node2: 98+100=198
|
|
456
447
|
// Diff = 2, within threshold 5
|
|
457
448
|
expect(snap.snappedPosition.x).toBe(100) // snapped
|
|
458
449
|
})
|
|
459
450
|
|
|
460
|
-
it(
|
|
451
|
+
it("snaps to center Y alignment", () => {
|
|
461
452
|
const flow = createFlow({
|
|
462
453
|
nodes: [
|
|
463
454
|
{
|
|
464
|
-
id:
|
|
455
|
+
id: "1",
|
|
465
456
|
position: { x: 0, y: 100 },
|
|
466
457
|
width: 100,
|
|
467
458
|
height: 50,
|
|
468
459
|
data: {},
|
|
469
460
|
},
|
|
470
461
|
{
|
|
471
|
-
id:
|
|
462
|
+
id: "2",
|
|
472
463
|
position: { x: 200, y: 0 },
|
|
473
464
|
width: 100,
|
|
474
465
|
height: 50,
|
|
@@ -479,22 +470,22 @@ describe('createFlow — advanced', () => {
|
|
|
479
470
|
|
|
480
471
|
// center Y of node1 = 100+25 = 125
|
|
481
472
|
// Move node2 so its center Y is close: y + 25 ≈ 125 → y ≈ 100
|
|
482
|
-
const snap = flow.getSnapLines(
|
|
473
|
+
const snap = flow.getSnapLines("2", { x: 200, y: 102 })
|
|
483
474
|
expect(snap.snappedPosition.y).toBe(100) // snapped to center Y
|
|
484
475
|
})
|
|
485
476
|
|
|
486
|
-
it(
|
|
477
|
+
it("snaps to bottom edge alignment", () => {
|
|
487
478
|
const flow = createFlow({
|
|
488
479
|
nodes: [
|
|
489
480
|
{
|
|
490
|
-
id:
|
|
481
|
+
id: "1",
|
|
491
482
|
position: { x: 0, y: 0 },
|
|
492
483
|
width: 100,
|
|
493
484
|
height: 50,
|
|
494
485
|
data: {},
|
|
495
486
|
},
|
|
496
487
|
{
|
|
497
|
-
id:
|
|
488
|
+
id: "2",
|
|
498
489
|
position: { x: 200, y: 0 },
|
|
499
490
|
width: 100,
|
|
500
491
|
height: 50,
|
|
@@ -505,75 +496,75 @@ describe('createFlow — advanced', () => {
|
|
|
505
496
|
|
|
506
497
|
// bottom of node1 = 50, bottom of node2 = y + 50
|
|
507
498
|
// For snap: abs(y+50 - 50) < 5 → abs(y) < 5
|
|
508
|
-
const snap = flow.getSnapLines(
|
|
499
|
+
const snap = flow.getSnapLines("2", { x: 200, y: 3 })
|
|
509
500
|
expect(snap.snappedPosition.y).toBe(0) // snapped to bottom edge
|
|
510
501
|
})
|
|
511
502
|
|
|
512
|
-
it(
|
|
503
|
+
it("returns original position for missing node", () => {
|
|
513
504
|
const flow = createFlow()
|
|
514
|
-
const snap = flow.getSnapLines(
|
|
505
|
+
const snap = flow.getSnapLines("missing", { x: 100, y: 200 })
|
|
515
506
|
expect(snap.snappedPosition).toEqual({ x: 100, y: 200 })
|
|
516
507
|
})
|
|
517
508
|
})
|
|
518
509
|
|
|
519
|
-
describe(
|
|
520
|
-
it(
|
|
510
|
+
describe("reconnectEdge", () => {
|
|
511
|
+
it("changes edge source", () => {
|
|
521
512
|
const flow = createFlow({
|
|
522
513
|
nodes: [
|
|
523
|
-
{ id:
|
|
524
|
-
{ id:
|
|
525
|
-
{ id:
|
|
514
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
515
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
516
|
+
{ id: "3", position: { x: 200, y: 0 }, data: {} },
|
|
526
517
|
],
|
|
527
|
-
edges: [{ id:
|
|
518
|
+
edges: [{ id: "e1", source: "1", target: "2" }],
|
|
528
519
|
})
|
|
529
520
|
|
|
530
|
-
flow.reconnectEdge(
|
|
531
|
-
expect(flow.getEdge(
|
|
532
|
-
expect(flow.getEdge(
|
|
521
|
+
flow.reconnectEdge("e1", { source: "3" })
|
|
522
|
+
expect(flow.getEdge("e1")!.source).toBe("3")
|
|
523
|
+
expect(flow.getEdge("e1")!.target).toBe("2") // unchanged
|
|
533
524
|
})
|
|
534
525
|
|
|
535
|
-
it(
|
|
526
|
+
it("changes edge target", () => {
|
|
536
527
|
const flow = createFlow({
|
|
537
528
|
nodes: [
|
|
538
|
-
{ id:
|
|
539
|
-
{ id:
|
|
540
|
-
{ id:
|
|
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: {} },
|
|
541
532
|
],
|
|
542
|
-
edges: [{ id:
|
|
533
|
+
edges: [{ id: "e1", source: "1", target: "2" }],
|
|
543
534
|
})
|
|
544
535
|
|
|
545
|
-
flow.reconnectEdge(
|
|
546
|
-
expect(flow.getEdge(
|
|
536
|
+
flow.reconnectEdge("e1", { target: "3" })
|
|
537
|
+
expect(flow.getEdge("e1")!.target).toBe("3")
|
|
547
538
|
})
|
|
548
539
|
})
|
|
549
540
|
|
|
550
|
-
describe(
|
|
551
|
-
it(
|
|
541
|
+
describe("edge with custom id and handle", () => {
|
|
542
|
+
it("generates id from source/target handles", () => {
|
|
552
543
|
const flow = createFlow({
|
|
553
544
|
nodes: [
|
|
554
|
-
{ id:
|
|
555
|
-
{ id:
|
|
545
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
546
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
556
547
|
],
|
|
557
548
|
edges: [
|
|
558
549
|
{
|
|
559
|
-
source:
|
|
560
|
-
target:
|
|
561
|
-
sourceHandle:
|
|
562
|
-
targetHandle:
|
|
550
|
+
source: "1",
|
|
551
|
+
target: "2",
|
|
552
|
+
sourceHandle: "out",
|
|
553
|
+
targetHandle: "in",
|
|
563
554
|
},
|
|
564
555
|
],
|
|
565
556
|
})
|
|
566
557
|
|
|
567
|
-
expect(flow.edges()[0]!.id).toBe(
|
|
558
|
+
expect(flow.edges()[0]!.id).toBe("e-1-out-2-in")
|
|
568
559
|
})
|
|
569
560
|
})
|
|
570
561
|
|
|
571
|
-
describe(
|
|
572
|
-
it(
|
|
562
|
+
describe("fitView with initial config", () => {
|
|
563
|
+
it("fits view on creation when config.fitView is true", () => {
|
|
573
564
|
const flow = createFlow({
|
|
574
565
|
nodes: [
|
|
575
|
-
{ id:
|
|
576
|
-
{ id:
|
|
566
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
567
|
+
{ id: "2", position: { x: 500, y: 500 }, data: {} },
|
|
577
568
|
],
|
|
578
569
|
fitView: true,
|
|
579
570
|
})
|
|
@@ -584,19 +575,19 @@ describe('createFlow — advanced', () => {
|
|
|
584
575
|
})
|
|
585
576
|
})
|
|
586
577
|
|
|
587
|
-
describe(
|
|
588
|
-
it(
|
|
578
|
+
describe("proximity connect with connection rules", () => {
|
|
579
|
+
it("respects connection rules", () => {
|
|
589
580
|
const flow = createFlow({
|
|
590
581
|
nodes: [
|
|
591
582
|
{
|
|
592
|
-
id:
|
|
593
|
-
type:
|
|
583
|
+
id: "1",
|
|
584
|
+
type: "output",
|
|
594
585
|
position: { x: 0, y: 0 },
|
|
595
586
|
data: {},
|
|
596
587
|
},
|
|
597
588
|
{
|
|
598
|
-
id:
|
|
599
|
-
type:
|
|
589
|
+
id: "2",
|
|
590
|
+
type: "input",
|
|
600
591
|
position: { x: 100, y: 0 },
|
|
601
592
|
data: {},
|
|
602
593
|
},
|
|
@@ -606,37 +597,37 @@ describe('createFlow — advanced', () => {
|
|
|
606
597
|
},
|
|
607
598
|
})
|
|
608
599
|
|
|
609
|
-
expect(flow.getProximityConnection(
|
|
600
|
+
expect(flow.getProximityConnection("1", 200)).toBeNull()
|
|
610
601
|
})
|
|
611
602
|
})
|
|
612
603
|
|
|
613
|
-
describe(
|
|
614
|
-
it(
|
|
604
|
+
describe("undo/redo edge cases", () => {
|
|
605
|
+
it("undo with empty history does nothing", () => {
|
|
615
606
|
const flow = createFlow({
|
|
616
|
-
nodes: [{ id:
|
|
607
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
617
608
|
})
|
|
618
609
|
|
|
619
610
|
flow.undo()
|
|
620
611
|
expect(flow.nodes()).toHaveLength(1)
|
|
621
612
|
})
|
|
622
613
|
|
|
623
|
-
it(
|
|
614
|
+
it("redo with empty redo stack does nothing", () => {
|
|
624
615
|
const flow = createFlow({
|
|
625
|
-
nodes: [{ id:
|
|
616
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
626
617
|
})
|
|
627
618
|
|
|
628
619
|
flow.redo()
|
|
629
620
|
expect(flow.nodes()).toHaveLength(1)
|
|
630
621
|
})
|
|
631
622
|
|
|
632
|
-
it(
|
|
623
|
+
it("multiple undo/redo cycles", () => {
|
|
633
624
|
const flow = createFlow()
|
|
634
625
|
|
|
635
626
|
flow.pushHistory()
|
|
636
|
-
flow.addNode({ id:
|
|
627
|
+
flow.addNode({ id: "1", position: { x: 0, y: 0 }, data: {} })
|
|
637
628
|
|
|
638
629
|
flow.pushHistory()
|
|
639
|
-
flow.addNode({ id:
|
|
630
|
+
flow.addNode({ id: "2", position: { x: 100, y: 0 }, data: {} })
|
|
640
631
|
|
|
641
632
|
expect(flow.nodes()).toHaveLength(2)
|
|
642
633
|
|
|
@@ -654,22 +645,22 @@ describe('createFlow — advanced', () => {
|
|
|
654
645
|
})
|
|
655
646
|
})
|
|
656
647
|
|
|
657
|
-
describe(
|
|
658
|
-
it(
|
|
648
|
+
describe("copy/paste with edges", () => {
|
|
649
|
+
it("copies connected edges between selected nodes", () => {
|
|
659
650
|
const flow = createFlow({
|
|
660
651
|
nodes: [
|
|
661
|
-
{ id:
|
|
662
|
-
{ id:
|
|
663
|
-
{ id:
|
|
652
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
653
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
654
|
+
{ id: "3", position: { x: 200, y: 0 }, data: {} },
|
|
664
655
|
],
|
|
665
656
|
edges: [
|
|
666
|
-
{ source:
|
|
667
|
-
{ source:
|
|
657
|
+
{ source: "1", target: "2" },
|
|
658
|
+
{ source: "2", target: "3" },
|
|
668
659
|
],
|
|
669
660
|
})
|
|
670
661
|
|
|
671
|
-
flow.selectNode(
|
|
672
|
-
flow.selectNode(
|
|
662
|
+
flow.selectNode("1")
|
|
663
|
+
flow.selectNode("2", true)
|
|
673
664
|
flow.copySelected()
|
|
674
665
|
flow.paste()
|
|
675
666
|
|
|
@@ -679,9 +670,9 @@ describe('createFlow — advanced', () => {
|
|
|
679
670
|
expect(flow.edges()).toHaveLength(3)
|
|
680
671
|
})
|
|
681
672
|
|
|
682
|
-
it(
|
|
673
|
+
it("copy with no selection does nothing", () => {
|
|
683
674
|
const flow = createFlow({
|
|
684
|
-
nodes: [{ id:
|
|
675
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
685
676
|
})
|
|
686
677
|
|
|
687
678
|
flow.copySelected()
|
|
@@ -690,10 +681,10 @@ describe('createFlow — advanced', () => {
|
|
|
690
681
|
})
|
|
691
682
|
})
|
|
692
683
|
|
|
693
|
-
describe(
|
|
694
|
-
it(
|
|
684
|
+
describe("listener callbacks", () => {
|
|
685
|
+
it("onNodeDragStart/End can be registered", () => {
|
|
695
686
|
const flow = createFlow({
|
|
696
|
-
nodes: [{ id:
|
|
687
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
697
688
|
})
|
|
698
689
|
|
|
699
690
|
const starts: string[] = []
|
|
@@ -703,32 +694,32 @@ describe('createFlow — advanced', () => {
|
|
|
703
694
|
flow.onNodeDragEnd((n) => ends.push(n.id))
|
|
704
695
|
|
|
705
696
|
// Emit manually via _emit
|
|
706
|
-
flow._emit.nodeDragStart(flow.getNode(
|
|
707
|
-
flow._emit.nodeDragEnd(flow.getNode(
|
|
697
|
+
flow._emit.nodeDragStart(flow.getNode("1")!)
|
|
698
|
+
flow._emit.nodeDragEnd(flow.getNode("1")!)
|
|
708
699
|
|
|
709
|
-
expect(starts).toEqual([
|
|
710
|
-
expect(ends).toEqual([
|
|
700
|
+
expect(starts).toEqual(["1"])
|
|
701
|
+
expect(ends).toEqual(["1"])
|
|
711
702
|
})
|
|
712
703
|
|
|
713
|
-
it(
|
|
704
|
+
it("onNodeDoubleClick", () => {
|
|
714
705
|
const flow = createFlow({
|
|
715
|
-
nodes: [{ id:
|
|
706
|
+
nodes: [{ id: "1", position: { x: 0, y: 0 }, data: {} }],
|
|
716
707
|
})
|
|
717
708
|
|
|
718
709
|
const clicked: string[] = []
|
|
719
710
|
flow.onNodeDoubleClick((n) => clicked.push(n.id))
|
|
720
711
|
|
|
721
|
-
flow._emit.nodeDoubleClick(flow.getNode(
|
|
722
|
-
expect(clicked).toEqual([
|
|
712
|
+
flow._emit.nodeDoubleClick(flow.getNode("1")!)
|
|
713
|
+
expect(clicked).toEqual(["1"])
|
|
723
714
|
})
|
|
724
715
|
|
|
725
|
-
it(
|
|
716
|
+
it("onNodeClick / onEdgeClick", () => {
|
|
726
717
|
const flow = createFlow({
|
|
727
718
|
nodes: [
|
|
728
|
-
{ id:
|
|
729
|
-
{ id:
|
|
719
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
720
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
730
721
|
],
|
|
731
|
-
edges: [{ id:
|
|
722
|
+
edges: [{ id: "e1", source: "1", target: "2" }],
|
|
732
723
|
})
|
|
733
724
|
|
|
734
725
|
const nodeClicks: string[] = []
|
|
@@ -737,29 +728,29 @@ describe('createFlow — advanced', () => {
|
|
|
737
728
|
flow.onNodeClick((n) => nodeClicks.push(n.id))
|
|
738
729
|
flow.onEdgeClick((e) => edgeClicks.push(e.id!))
|
|
739
730
|
|
|
740
|
-
flow._emit.nodeClick(flow.getNode(
|
|
741
|
-
flow._emit.edgeClick(flow.getEdge(
|
|
731
|
+
flow._emit.nodeClick(flow.getNode("1")!)
|
|
732
|
+
flow._emit.edgeClick(flow.getEdge("e1")!)
|
|
742
733
|
|
|
743
|
-
expect(nodeClicks).toEqual([
|
|
744
|
-
expect(edgeClicks).toEqual([
|
|
734
|
+
expect(nodeClicks).toEqual(["1"])
|
|
735
|
+
expect(edgeClicks).toEqual(["e1"])
|
|
745
736
|
})
|
|
746
737
|
})
|
|
747
738
|
|
|
748
|
-
describe(
|
|
749
|
-
it(
|
|
739
|
+
describe("containerSize", () => {
|
|
740
|
+
it("containerSize signal exists with defaults", () => {
|
|
750
741
|
const flow = createFlow()
|
|
751
742
|
expect(flow.containerSize()).toEqual({ width: 800, height: 600 })
|
|
752
743
|
})
|
|
753
744
|
|
|
754
|
-
it(
|
|
745
|
+
it("containerSize can be updated", () => {
|
|
755
746
|
const flow = createFlow()
|
|
756
747
|
flow.containerSize.set({ width: 1200, height: 900 })
|
|
757
748
|
expect(flow.containerSize()).toEqual({ width: 1200, height: 900 })
|
|
758
749
|
})
|
|
759
750
|
})
|
|
760
751
|
|
|
761
|
-
describe(
|
|
762
|
-
it(
|
|
752
|
+
describe("clampToExtent", () => {
|
|
753
|
+
it("returns position unchanged when no extent", () => {
|
|
763
754
|
const flow = createFlow()
|
|
764
755
|
expect(flow.clampToExtent({ x: -999, y: -999 })).toEqual({
|
|
765
756
|
x: -999,
|
|
@@ -767,7 +758,7 @@ describe('createFlow — advanced', () => {
|
|
|
767
758
|
})
|
|
768
759
|
})
|
|
769
760
|
|
|
770
|
-
it(
|
|
761
|
+
it("clamps to extent boundaries", () => {
|
|
771
762
|
const flow = createFlow({
|
|
772
763
|
nodeExtent: [
|
|
773
764
|
[0, 0],
|
|
@@ -785,18 +776,18 @@ describe('createFlow — advanced', () => {
|
|
|
785
776
|
})
|
|
786
777
|
})
|
|
787
778
|
|
|
788
|
-
describe(
|
|
789
|
-
it(
|
|
779
|
+
describe("edge default type", () => {
|
|
780
|
+
it("uses defaultEdgeType from config", () => {
|
|
790
781
|
const flow = createFlow({
|
|
791
782
|
nodes: [
|
|
792
|
-
{ id:
|
|
793
|
-
{ id:
|
|
783
|
+
{ id: "1", position: { x: 0, y: 0 }, data: {} },
|
|
784
|
+
{ id: "2", position: { x: 100, y: 0 }, data: {} },
|
|
794
785
|
],
|
|
795
|
-
edges: [{ source:
|
|
796
|
-
defaultEdgeType:
|
|
786
|
+
edges: [{ source: "1", target: "2" }],
|
|
787
|
+
defaultEdgeType: "straight",
|
|
797
788
|
})
|
|
798
789
|
|
|
799
|
-
expect(flow.edges()[0]!.type).toBe(
|
|
790
|
+
expect(flow.edges()[0]!.type).toBe("straight")
|
|
800
791
|
})
|
|
801
792
|
})
|
|
802
793
|
})
|