pict-section-flow 0.0.10 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/launch.json +1 -1
- package/README.md +176 -0
- package/docs/.nojekyll +0 -0
- package/docs/Architecture.md +303 -0
- package/docs/Custom-Styling.md +275 -0
- package/docs/Data_Model.md +158 -0
- package/docs/Event_System.md +156 -0
- package/docs/Getting_Started.md +237 -0
- package/docs/Implementation_Reference.md +528 -0
- package/docs/Layout_Persistence.md +117 -0
- package/docs/README.md +115 -52
- package/docs/_cover.md +11 -0
- package/docs/_sidebar.md +52 -0
- package/docs/_topbar.md +8 -0
- package/docs/api/PictFlowCard.md +216 -0
- package/docs/api/PictFlowCardPropertiesPanel.md +235 -0
- package/docs/api/addConnection.md +101 -0
- package/docs/api/addNode.md +137 -0
- package/docs/api/autoLayout.md +77 -0
- package/docs/api/getFlowData.md +112 -0
- package/docs/api/marshalToView.md +95 -0
- package/docs/api/openPanel.md +128 -0
- package/docs/api/registerHandler.md +174 -0
- package/docs/api/registerNodeType.md +142 -0
- package/docs/api/removeConnection.md +57 -0
- package/docs/api/removeNode.md +80 -0
- package/docs/api/saveLayout.md +152 -0
- package/docs/api/screenToSVGCoords.md +68 -0
- package/docs/api/selectNode.md +116 -0
- package/docs/api/setTheme.md +168 -0
- package/docs/api/setZoom.md +97 -0
- package/docs/api/toggleFullscreen.md +68 -0
- package/docs/card-help/EACH.md +19 -0
- package/docs/card-help/FREAD.md +24 -0
- package/docs/card-help/FWRITE.md +24 -0
- package/docs/card-help/GET.md +22 -0
- package/docs/card-help/ITE.md +23 -0
- package/docs/card-help/LOG.md +23 -0
- package/docs/card-help/NOTE.md +17 -0
- package/docs/card-help/PREV.md +18 -0
- package/docs/card-help/SET.md +27 -0
- package/docs/card-help/SPKL.md +22 -0
- package/docs/card-help/STAT.md +23 -0
- package/docs/card-help/SW.md +25 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +169 -0
- package/docs/retold-keyword-index.json +13942 -0
- package/example_applications/simple_cards/package.json +1 -0
- package/example_applications/simple_cards/source/card-help-content.js +16 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +2 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +2 -0
- package/package.json +11 -7
- package/scripts/generate-card-help.js +214 -0
- package/source/Pict-Section-Flow.js +4 -0
- package/source/PictFlowCard.js +3 -1
- package/source/providers/PictProvider-Flow-CSS.js +245 -152
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +24 -0
- package/source/providers/PictProvider-Flow-Geometry.js +195 -38
- package/source/providers/PictProvider-Flow-PanelChrome.js +14 -12
- package/source/services/PictService-Flow-ConnectionHandleManager.js +263 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +134 -183
- package/source/services/PictService-Flow-DataManager.js +338 -0
- package/source/services/PictService-Flow-InteractionManager.js +165 -7
- package/source/services/PictService-Flow-PathGenerator.js +282 -0
- package/source/services/PictService-Flow-PortRenderer.js +269 -0
- package/source/services/PictService-Flow-RenderManager.js +281 -0
- package/source/services/PictService-Flow-Tether.js +6 -42
- package/source/views/PictView-Flow-Node.js +2 -220
- package/source/views/PictView-Flow-PropertiesPanel.js +89 -44
- package/source/views/PictView-Flow.js +130 -882
- package/test/ConnectionHandleManager_tests.js +717 -0
- package/test/ConnectionRenderer_tests.js +591 -0
- package/test/DataManager_tests.js +859 -0
- package/test/Geometry_tests.js +767 -0
- package/test/PathGenerator_tests.js +978 -0
- package/test/PortRenderer_tests.js +367 -0
- package/test/RenderManager_tests.js +756 -0
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
const libFable = require('fable');
|
|
2
|
+
const libChai = require('chai');
|
|
3
|
+
const libExpect = libChai.expect;
|
|
4
|
+
|
|
5
|
+
const libDataManager = require('../source/services/PictService-Flow-DataManager.js');
|
|
6
|
+
|
|
7
|
+
suite
|
|
8
|
+
(
|
|
9
|
+
'PictService-Flow-DataManager',
|
|
10
|
+
function ()
|
|
11
|
+
{
|
|
12
|
+
let _Fable;
|
|
13
|
+
let _DataManager;
|
|
14
|
+
let _MockFlowView;
|
|
15
|
+
|
|
16
|
+
setup
|
|
17
|
+
(
|
|
18
|
+
function ()
|
|
19
|
+
{
|
|
20
|
+
_Fable = new libFable({});
|
|
21
|
+
|
|
22
|
+
_MockFlowView =
|
|
23
|
+
{
|
|
24
|
+
fable: _Fable,
|
|
25
|
+
pict: null,
|
|
26
|
+
log: _Fable.log,
|
|
27
|
+
options:
|
|
28
|
+
{
|
|
29
|
+
DefaultNodeType: 'generic',
|
|
30
|
+
DefaultNodeWidth: 160,
|
|
31
|
+
DefaultNodeHeight: 80,
|
|
32
|
+
FlowDataAddress: null
|
|
33
|
+
},
|
|
34
|
+
Bundle: {},
|
|
35
|
+
initialRenderComplete: false,
|
|
36
|
+
_FlowData:
|
|
37
|
+
{
|
|
38
|
+
Nodes: [],
|
|
39
|
+
Connections: [],
|
|
40
|
+
OpenPanels: [],
|
|
41
|
+
SavedLayouts: [],
|
|
42
|
+
ViewState:
|
|
43
|
+
{
|
|
44
|
+
PanX: 0,
|
|
45
|
+
PanY: 0,
|
|
46
|
+
Zoom: 1,
|
|
47
|
+
SelectedNodeHash: null,
|
|
48
|
+
SelectedConnectionHash: null,
|
|
49
|
+
SelectedTetherHash: null
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
_NodeTypeProvider:
|
|
53
|
+
{
|
|
54
|
+
getNodeType: function (pType)
|
|
55
|
+
{
|
|
56
|
+
if (pType === 'action')
|
|
57
|
+
{
|
|
58
|
+
return {
|
|
59
|
+
Label: 'Action',
|
|
60
|
+
DefaultWidth: 200,
|
|
61
|
+
DefaultHeight: 100,
|
|
62
|
+
DefaultPorts:
|
|
63
|
+
[
|
|
64
|
+
{ Hash: 'p-in', Direction: 'input', Side: 'left', Label: 'In' },
|
|
65
|
+
{ Hash: 'p-out', Direction: 'output', Side: 'right', Label: 'Out' },
|
|
66
|
+
{ Hash: 'p-err', Direction: 'output', Side: 'bottom', Label: 'Error', PortType: 'error' }
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
_LayoutProvider:
|
|
74
|
+
{
|
|
75
|
+
loadPersistedLayouts: function () {}
|
|
76
|
+
},
|
|
77
|
+
_EventHandlerProvider:
|
|
78
|
+
{
|
|
79
|
+
fireEvent: function () {}
|
|
80
|
+
},
|
|
81
|
+
renderFlow: function () {},
|
|
82
|
+
closePanelForNode: function (pNodeHash)
|
|
83
|
+
{
|
|
84
|
+
_MockFlowView._FlowData.OpenPanels = _MockFlowView._FlowData.OpenPanels.filter(
|
|
85
|
+
(pPanel) => pPanel.NodeHash !== pNodeHash
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
marshalFromView: function () {}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
_DataManager = new libDataManager(_Fable, { FlowView: _MockFlowView }, 'DM-Test');
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// ---- Constructor ----
|
|
96
|
+
|
|
97
|
+
suite
|
|
98
|
+
(
|
|
99
|
+
'Constructor',
|
|
100
|
+
function ()
|
|
101
|
+
{
|
|
102
|
+
test
|
|
103
|
+
(
|
|
104
|
+
'should instantiate with correct serviceType',
|
|
105
|
+
function (fDone)
|
|
106
|
+
{
|
|
107
|
+
libExpect(_DataManager).to.be.an('object');
|
|
108
|
+
libExpect(_DataManager.serviceType).to.equal('PictServiceFlowDataManager');
|
|
109
|
+
fDone();
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
test
|
|
114
|
+
(
|
|
115
|
+
'should store FlowView reference from options',
|
|
116
|
+
function (fDone)
|
|
117
|
+
{
|
|
118
|
+
libExpect(_DataManager._FlowView).to.equal(_MockFlowView);
|
|
119
|
+
fDone();
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
test
|
|
124
|
+
(
|
|
125
|
+
'should handle missing FlowView in options',
|
|
126
|
+
function (fDone)
|
|
127
|
+
{
|
|
128
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
129
|
+
libExpect(tmpManager._FlowView).to.be.null;
|
|
130
|
+
fDone();
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// ---- getFlowData ----
|
|
137
|
+
|
|
138
|
+
suite
|
|
139
|
+
(
|
|
140
|
+
'getFlowData',
|
|
141
|
+
function ()
|
|
142
|
+
{
|
|
143
|
+
test
|
|
144
|
+
(
|
|
145
|
+
'should return a deep clone of the flow data',
|
|
146
|
+
function (fDone)
|
|
147
|
+
{
|
|
148
|
+
_MockFlowView._FlowData.Nodes.push({ Hash: 'node-1', X: 10, Y: 20 });
|
|
149
|
+
let tmpResult = _DataManager.getFlowData();
|
|
150
|
+
|
|
151
|
+
libExpect(tmpResult.Nodes).to.have.length(1);
|
|
152
|
+
libExpect(tmpResult.Nodes[0].Hash).to.equal('node-1');
|
|
153
|
+
|
|
154
|
+
// Verify it's a clone, not a reference
|
|
155
|
+
tmpResult.Nodes[0].X = 999;
|
|
156
|
+
libExpect(_MockFlowView._FlowData.Nodes[0].X).to.equal(10);
|
|
157
|
+
fDone();
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
test
|
|
162
|
+
(
|
|
163
|
+
'should return empty object when no FlowView',
|
|
164
|
+
function (fDone)
|
|
165
|
+
{
|
|
166
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
167
|
+
let tmpResult = tmpManager.getFlowData();
|
|
168
|
+
libExpect(tmpResult).to.deep.equal({});
|
|
169
|
+
fDone();
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// ---- setFlowData ----
|
|
176
|
+
|
|
177
|
+
suite
|
|
178
|
+
(
|
|
179
|
+
'setFlowData',
|
|
180
|
+
function ()
|
|
181
|
+
{
|
|
182
|
+
test
|
|
183
|
+
(
|
|
184
|
+
'should set flow data with validated structure',
|
|
185
|
+
function (fDone)
|
|
186
|
+
{
|
|
187
|
+
let tmpData =
|
|
188
|
+
{
|
|
189
|
+
Nodes: [{ Hash: 'n1', X: 50, Y: 50 }],
|
|
190
|
+
Connections: [{ Hash: 'c1', SourceNodeHash: 'n1', TargetNodeHash: 'n2' }],
|
|
191
|
+
ViewState: { PanX: 100, PanY: 200, Zoom: 2 }
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
_DataManager.setFlowData(tmpData);
|
|
195
|
+
|
|
196
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.have.length(1);
|
|
197
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(1);
|
|
198
|
+
libExpect(_MockFlowView._FlowData.ViewState.PanX).to.equal(100);
|
|
199
|
+
libExpect(_MockFlowView._FlowData.ViewState.Zoom).to.equal(2);
|
|
200
|
+
fDone();
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
test
|
|
205
|
+
(
|
|
206
|
+
'should provide defaults for missing arrays',
|
|
207
|
+
function (fDone)
|
|
208
|
+
{
|
|
209
|
+
_DataManager.setFlowData({ ViewState: { PanX: 10 } });
|
|
210
|
+
|
|
211
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.be.an('array').that.is.empty;
|
|
212
|
+
libExpect(_MockFlowView._FlowData.Connections).to.be.an('array').that.is.empty;
|
|
213
|
+
libExpect(_MockFlowView._FlowData.OpenPanels).to.be.an('array').that.is.empty;
|
|
214
|
+
libExpect(_MockFlowView._FlowData.SavedLayouts).to.be.an('array').that.is.empty;
|
|
215
|
+
fDone();
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
test
|
|
220
|
+
(
|
|
221
|
+
'should provide default ViewState when missing',
|
|
222
|
+
function (fDone)
|
|
223
|
+
{
|
|
224
|
+
_DataManager.setFlowData({ Nodes: [] });
|
|
225
|
+
|
|
226
|
+
libExpect(_MockFlowView._FlowData.ViewState.PanX).to.equal(0);
|
|
227
|
+
libExpect(_MockFlowView._FlowData.ViewState.PanY).to.equal(0);
|
|
228
|
+
libExpect(_MockFlowView._FlowData.ViewState.Zoom).to.equal(1);
|
|
229
|
+
libExpect(_MockFlowView._FlowData.ViewState.SelectedNodeHash).to.be.null;
|
|
230
|
+
fDone();
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
test
|
|
235
|
+
(
|
|
236
|
+
'should reject null data',
|
|
237
|
+
function (fDone)
|
|
238
|
+
{
|
|
239
|
+
let tmpOriginalNodes = _MockFlowView._FlowData.Nodes;
|
|
240
|
+
_DataManager.setFlowData(null);
|
|
241
|
+
// Should not have changed
|
|
242
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.equal(tmpOriginalNodes);
|
|
243
|
+
fDone();
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
test
|
|
248
|
+
(
|
|
249
|
+
'should reject non-object data',
|
|
250
|
+
function (fDone)
|
|
251
|
+
{
|
|
252
|
+
let tmpOriginalNodes = _MockFlowView._FlowData.Nodes;
|
|
253
|
+
_DataManager.setFlowData('invalid');
|
|
254
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.equal(tmpOriginalNodes);
|
|
255
|
+
fDone();
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
test
|
|
260
|
+
(
|
|
261
|
+
'should call renderFlow when initialRenderComplete is true',
|
|
262
|
+
function (fDone)
|
|
263
|
+
{
|
|
264
|
+
let tmpRenderCalled = false;
|
|
265
|
+
_MockFlowView.renderFlow = function () { tmpRenderCalled = true; };
|
|
266
|
+
_MockFlowView.initialRenderComplete = true;
|
|
267
|
+
|
|
268
|
+
_DataManager.setFlowData({ Nodes: [] });
|
|
269
|
+
|
|
270
|
+
libExpect(tmpRenderCalled).to.be.true;
|
|
271
|
+
fDone();
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
test
|
|
276
|
+
(
|
|
277
|
+
'should not call renderFlow when initialRenderComplete is false',
|
|
278
|
+
function (fDone)
|
|
279
|
+
{
|
|
280
|
+
let tmpRenderCalled = false;
|
|
281
|
+
_MockFlowView.renderFlow = function () { tmpRenderCalled = true; };
|
|
282
|
+
_MockFlowView.initialRenderComplete = false;
|
|
283
|
+
|
|
284
|
+
_DataManager.setFlowData({ Nodes: [] });
|
|
285
|
+
|
|
286
|
+
libExpect(tmpRenderCalled).to.be.false;
|
|
287
|
+
fDone();
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
test
|
|
292
|
+
(
|
|
293
|
+
'should call loadPersistedLayouts when LayoutProvider exists',
|
|
294
|
+
function (fDone)
|
|
295
|
+
{
|
|
296
|
+
let tmpLoadCalled = false;
|
|
297
|
+
_MockFlowView._LayoutProvider.loadPersistedLayouts = function () { tmpLoadCalled = true; };
|
|
298
|
+
|
|
299
|
+
_DataManager.setFlowData({ Nodes: [] });
|
|
300
|
+
|
|
301
|
+
libExpect(tmpLoadCalled).to.be.true;
|
|
302
|
+
fDone();
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// ---- addNode ----
|
|
309
|
+
|
|
310
|
+
suite
|
|
311
|
+
(
|
|
312
|
+
'addNode',
|
|
313
|
+
function ()
|
|
314
|
+
{
|
|
315
|
+
test
|
|
316
|
+
(
|
|
317
|
+
'should add a node with generated hash',
|
|
318
|
+
function (fDone)
|
|
319
|
+
{
|
|
320
|
+
let tmpNode = _DataManager.addNode('generic', 200, 300, 'Test Node');
|
|
321
|
+
|
|
322
|
+
libExpect(tmpNode).to.be.an('object');
|
|
323
|
+
libExpect(tmpNode.Hash).to.be.a('string').that.includes('node-');
|
|
324
|
+
libExpect(tmpNode.X).to.equal(200);
|
|
325
|
+
libExpect(tmpNode.Y).to.equal(300);
|
|
326
|
+
libExpect(tmpNode.Title).to.equal('Test Node');
|
|
327
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.have.length(1);
|
|
328
|
+
fDone();
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
test
|
|
333
|
+
(
|
|
334
|
+
'should use node type config for defaults',
|
|
335
|
+
function (fDone)
|
|
336
|
+
{
|
|
337
|
+
let tmpNode = _DataManager.addNode('action', 100, 100);
|
|
338
|
+
|
|
339
|
+
libExpect(tmpNode.Width).to.equal(200);
|
|
340
|
+
libExpect(tmpNode.Height).to.equal(100);
|
|
341
|
+
libExpect(tmpNode.Title).to.equal('Action');
|
|
342
|
+
libExpect(tmpNode.Ports).to.have.length(3);
|
|
343
|
+
fDone();
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
test
|
|
348
|
+
(
|
|
349
|
+
'should use option defaults when type config is null',
|
|
350
|
+
function (fDone)
|
|
351
|
+
{
|
|
352
|
+
let tmpNode = _DataManager.addNode('unknown', 100, 100, 'My Node');
|
|
353
|
+
|
|
354
|
+
libExpect(tmpNode.Width).to.equal(160);
|
|
355
|
+
libExpect(tmpNode.Height).to.equal(80);
|
|
356
|
+
libExpect(tmpNode.Title).to.equal('My Node');
|
|
357
|
+
// Default ports (2 generic)
|
|
358
|
+
libExpect(tmpNode.Ports).to.have.length(2);
|
|
359
|
+
fDone();
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
test
|
|
364
|
+
(
|
|
365
|
+
'should use DefaultNodeType when type is null',
|
|
366
|
+
function (fDone)
|
|
367
|
+
{
|
|
368
|
+
let tmpNode = _DataManager.addNode(null, 100, 100, 'Fallback');
|
|
369
|
+
|
|
370
|
+
libExpect(tmpNode.Type).to.equal('generic');
|
|
371
|
+
fDone();
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
test
|
|
376
|
+
(
|
|
377
|
+
'should default position to 100,100 when not provided',
|
|
378
|
+
function (fDone)
|
|
379
|
+
{
|
|
380
|
+
let tmpNode = _DataManager.addNode('generic', 0, 0);
|
|
381
|
+
|
|
382
|
+
// 0 is falsy, so it should fall back to 100
|
|
383
|
+
// (This tests the || operator behavior)
|
|
384
|
+
libExpect(tmpNode.X).to.equal(100);
|
|
385
|
+
libExpect(tmpNode.Y).to.equal(100);
|
|
386
|
+
fDone();
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
test
|
|
391
|
+
(
|
|
392
|
+
'should ensure all ports have unique hashes',
|
|
393
|
+
function (fDone)
|
|
394
|
+
{
|
|
395
|
+
let tmpNode = _DataManager.addNode('action', 100, 100);
|
|
396
|
+
let tmpHashes = tmpNode.Ports.map((p) => p.Hash);
|
|
397
|
+
let tmpUnique = new Set(tmpHashes);
|
|
398
|
+
libExpect(tmpUnique.size).to.equal(tmpHashes.length);
|
|
399
|
+
fDone();
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
test
|
|
404
|
+
(
|
|
405
|
+
'should call renderFlow and marshalFromView',
|
|
406
|
+
function (fDone)
|
|
407
|
+
{
|
|
408
|
+
let tmpRenderCalled = false;
|
|
409
|
+
let tmpMarshalCalled = false;
|
|
410
|
+
_MockFlowView.renderFlow = function () { tmpRenderCalled = true; };
|
|
411
|
+
_DataManager.marshalFromView = function () { tmpMarshalCalled = true; };
|
|
412
|
+
|
|
413
|
+
_DataManager.addNode('generic', 100, 100);
|
|
414
|
+
|
|
415
|
+
libExpect(tmpRenderCalled).to.be.true;
|
|
416
|
+
libExpect(tmpMarshalCalled).to.be.true;
|
|
417
|
+
fDone();
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
test
|
|
422
|
+
(
|
|
423
|
+
'should fire onNodeAdded and onFlowChanged events',
|
|
424
|
+
function (fDone)
|
|
425
|
+
{
|
|
426
|
+
let tmpEvents = [];
|
|
427
|
+
_MockFlowView._EventHandlerProvider.fireEvent = function (pEvent) { tmpEvents.push(pEvent); };
|
|
428
|
+
|
|
429
|
+
_DataManager.addNode('generic', 100, 100);
|
|
430
|
+
|
|
431
|
+
libExpect(tmpEvents).to.include('onNodeAdded');
|
|
432
|
+
libExpect(tmpEvents).to.include('onFlowChanged');
|
|
433
|
+
fDone();
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
test
|
|
438
|
+
(
|
|
439
|
+
'should return null when no FlowView',
|
|
440
|
+
function (fDone)
|
|
441
|
+
{
|
|
442
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
443
|
+
let tmpResult = tmpManager.addNode('generic', 100, 100);
|
|
444
|
+
libExpect(tmpResult).to.be.null;
|
|
445
|
+
fDone();
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
test
|
|
450
|
+
(
|
|
451
|
+
'should include custom data when provided',
|
|
452
|
+
function (fDone)
|
|
453
|
+
{
|
|
454
|
+
let tmpNode = _DataManager.addNode('generic', 100, 100, 'Test', { foo: 'bar' });
|
|
455
|
+
libExpect(tmpNode.Data).to.deep.equal({ foo: 'bar' });
|
|
456
|
+
fDone();
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// ---- removeNode ----
|
|
463
|
+
|
|
464
|
+
suite
|
|
465
|
+
(
|
|
466
|
+
'removeNode',
|
|
467
|
+
function ()
|
|
468
|
+
{
|
|
469
|
+
test
|
|
470
|
+
(
|
|
471
|
+
'should remove a node by hash',
|
|
472
|
+
function (fDone)
|
|
473
|
+
{
|
|
474
|
+
let tmpNode = _DataManager.addNode('generic', 100, 100, 'ToRemove');
|
|
475
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.have.length(1);
|
|
476
|
+
|
|
477
|
+
let tmpResult = _DataManager.removeNode(tmpNode.Hash);
|
|
478
|
+
libExpect(tmpResult).to.be.true;
|
|
479
|
+
libExpect(_MockFlowView._FlowData.Nodes).to.have.length(0);
|
|
480
|
+
fDone();
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
test
|
|
485
|
+
(
|
|
486
|
+
'should return false for non-existent node',
|
|
487
|
+
function (fDone)
|
|
488
|
+
{
|
|
489
|
+
let tmpResult = _DataManager.removeNode('non-existent');
|
|
490
|
+
libExpect(tmpResult).to.be.false;
|
|
491
|
+
fDone();
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
test
|
|
496
|
+
(
|
|
497
|
+
'should cascade-delete connections involving the node',
|
|
498
|
+
function (fDone)
|
|
499
|
+
{
|
|
500
|
+
// Set up two nodes with a connection
|
|
501
|
+
_MockFlowView._FlowData.Nodes = [
|
|
502
|
+
{ Hash: 'n1', Ports: [{ Hash: 'p1', Direction: 'output' }] },
|
|
503
|
+
{ Hash: 'n2', Ports: [{ Hash: 'p2', Direction: 'input' }] }
|
|
504
|
+
];
|
|
505
|
+
_MockFlowView._FlowData.Connections = [
|
|
506
|
+
{ Hash: 'c1', SourceNodeHash: 'n1', SourcePortHash: 'p1', TargetNodeHash: 'n2', TargetPortHash: 'p2' }
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
_DataManager.removeNode('n1');
|
|
510
|
+
|
|
511
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(0);
|
|
512
|
+
fDone();
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
test
|
|
517
|
+
(
|
|
518
|
+
'should close open panels for the node',
|
|
519
|
+
function (fDone)
|
|
520
|
+
{
|
|
521
|
+
_MockFlowView._FlowData.Nodes = [{ Hash: 'n1' }];
|
|
522
|
+
_MockFlowView._FlowData.OpenPanels = [{ Hash: 'panel-1', NodeHash: 'n1' }];
|
|
523
|
+
|
|
524
|
+
_DataManager.removeNode('n1');
|
|
525
|
+
|
|
526
|
+
libExpect(_MockFlowView._FlowData.OpenPanels).to.have.length(0);
|
|
527
|
+
fDone();
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
test
|
|
532
|
+
(
|
|
533
|
+
'should clear selection if removed node was selected',
|
|
534
|
+
function (fDone)
|
|
535
|
+
{
|
|
536
|
+
_MockFlowView._FlowData.Nodes = [{ Hash: 'n1' }];
|
|
537
|
+
_MockFlowView._FlowData.ViewState.SelectedNodeHash = 'n1';
|
|
538
|
+
|
|
539
|
+
_DataManager.removeNode('n1');
|
|
540
|
+
|
|
541
|
+
libExpect(_MockFlowView._FlowData.ViewState.SelectedNodeHash).to.be.null;
|
|
542
|
+
fDone();
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
test
|
|
547
|
+
(
|
|
548
|
+
'should fire onNodeRemoved and onFlowChanged events',
|
|
549
|
+
function (fDone)
|
|
550
|
+
{
|
|
551
|
+
_MockFlowView._FlowData.Nodes = [{ Hash: 'n1' }];
|
|
552
|
+
let tmpEvents = [];
|
|
553
|
+
_MockFlowView._EventHandlerProvider.fireEvent = function (pEvent) { tmpEvents.push(pEvent); };
|
|
554
|
+
|
|
555
|
+
_DataManager.removeNode('n1');
|
|
556
|
+
|
|
557
|
+
libExpect(tmpEvents).to.include('onNodeRemoved');
|
|
558
|
+
libExpect(tmpEvents).to.include('onFlowChanged');
|
|
559
|
+
fDone();
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
test
|
|
564
|
+
(
|
|
565
|
+
'should return false when no FlowView',
|
|
566
|
+
function (fDone)
|
|
567
|
+
{
|
|
568
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
569
|
+
let tmpResult = tmpManager.removeNode('n1');
|
|
570
|
+
libExpect(tmpResult).to.be.false;
|
|
571
|
+
fDone();
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// ---- addConnection ----
|
|
578
|
+
|
|
579
|
+
suite
|
|
580
|
+
(
|
|
581
|
+
'addConnection',
|
|
582
|
+
function ()
|
|
583
|
+
{
|
|
584
|
+
setup
|
|
585
|
+
(
|
|
586
|
+
function ()
|
|
587
|
+
{
|
|
588
|
+
// Set up two nodes with ports
|
|
589
|
+
_MockFlowView._FlowData.Nodes = [
|
|
590
|
+
{
|
|
591
|
+
Hash: 'n1',
|
|
592
|
+
Ports: [
|
|
593
|
+
{ Hash: 'p-out-1', Direction: 'output', Side: 'right', Label: 'Out' }
|
|
594
|
+
]
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
Hash: 'n2',
|
|
598
|
+
Ports: [
|
|
599
|
+
{ Hash: 'p-in-2', Direction: 'input', Side: 'left', Label: 'In' }
|
|
600
|
+
]
|
|
601
|
+
}
|
|
602
|
+
];
|
|
603
|
+
_MockFlowView._FlowData.Connections = [];
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
test
|
|
608
|
+
(
|
|
609
|
+
'should create a connection between valid nodes/ports',
|
|
610
|
+
function (fDone)
|
|
611
|
+
{
|
|
612
|
+
let tmpConn = _DataManager.addConnection('n1', 'p-out-1', 'n2', 'p-in-2');
|
|
613
|
+
|
|
614
|
+
libExpect(tmpConn).to.be.an('object');
|
|
615
|
+
libExpect(tmpConn.Hash).to.be.a('string').that.includes('conn-');
|
|
616
|
+
libExpect(tmpConn.SourceNodeHash).to.equal('n1');
|
|
617
|
+
libExpect(tmpConn.TargetNodeHash).to.equal('n2');
|
|
618
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(1);
|
|
619
|
+
fDone();
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
test
|
|
624
|
+
(
|
|
625
|
+
'should return false for missing source node',
|
|
626
|
+
function (fDone)
|
|
627
|
+
{
|
|
628
|
+
let tmpResult = _DataManager.addConnection('non-existent', 'p-out-1', 'n2', 'p-in-2');
|
|
629
|
+
libExpect(tmpResult).to.be.false;
|
|
630
|
+
fDone();
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
test
|
|
635
|
+
(
|
|
636
|
+
'should return false for missing target node',
|
|
637
|
+
function (fDone)
|
|
638
|
+
{
|
|
639
|
+
let tmpResult = _DataManager.addConnection('n1', 'p-out-1', 'non-existent', 'p-in-2');
|
|
640
|
+
libExpect(tmpResult).to.be.false;
|
|
641
|
+
fDone();
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
test
|
|
646
|
+
(
|
|
647
|
+
'should return false for missing source port',
|
|
648
|
+
function (fDone)
|
|
649
|
+
{
|
|
650
|
+
let tmpResult = _DataManager.addConnection('n1', 'bad-port', 'n2', 'p-in-2');
|
|
651
|
+
libExpect(tmpResult).to.be.false;
|
|
652
|
+
fDone();
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
test
|
|
657
|
+
(
|
|
658
|
+
'should return false for missing target port',
|
|
659
|
+
function (fDone)
|
|
660
|
+
{
|
|
661
|
+
let tmpResult = _DataManager.addConnection('n1', 'p-out-1', 'n2', 'bad-port');
|
|
662
|
+
libExpect(tmpResult).to.be.false;
|
|
663
|
+
fDone();
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
test
|
|
668
|
+
(
|
|
669
|
+
'should prevent self-connections',
|
|
670
|
+
function (fDone)
|
|
671
|
+
{
|
|
672
|
+
_MockFlowView._FlowData.Nodes[0].Ports.push(
|
|
673
|
+
{ Hash: 'p-in-1', Direction: 'input', Side: 'left', Label: 'In' }
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
let tmpResult = _DataManager.addConnection('n1', 'p-out-1', 'n1', 'p-in-1');
|
|
677
|
+
libExpect(tmpResult).to.be.false;
|
|
678
|
+
fDone();
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
test
|
|
683
|
+
(
|
|
684
|
+
'should prevent duplicate connections',
|
|
685
|
+
function (fDone)
|
|
686
|
+
{
|
|
687
|
+
_DataManager.addConnection('n1', 'p-out-1', 'n2', 'p-in-2');
|
|
688
|
+
let tmpResult = _DataManager.addConnection('n1', 'p-out-1', 'n2', 'p-in-2');
|
|
689
|
+
|
|
690
|
+
libExpect(tmpResult).to.be.false;
|
|
691
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(1);
|
|
692
|
+
fDone();
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
test
|
|
697
|
+
(
|
|
698
|
+
'should include custom data when provided',
|
|
699
|
+
function (fDone)
|
|
700
|
+
{
|
|
701
|
+
let tmpConn = _DataManager.addConnection('n1', 'p-out-1', 'n2', 'p-in-2', { LineMode: 'bezier' });
|
|
702
|
+
libExpect(tmpConn.Data).to.deep.equal({ LineMode: 'bezier' });
|
|
703
|
+
fDone();
|
|
704
|
+
}
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
test
|
|
708
|
+
(
|
|
709
|
+
'should fire onConnectionCreated and onFlowChanged events',
|
|
710
|
+
function (fDone)
|
|
711
|
+
{
|
|
712
|
+
let tmpEvents = [];
|
|
713
|
+
_MockFlowView._EventHandlerProvider.fireEvent = function (pEvent) { tmpEvents.push(pEvent); };
|
|
714
|
+
|
|
715
|
+
_DataManager.addConnection('n1', 'p-out-1', 'n2', 'p-in-2');
|
|
716
|
+
|
|
717
|
+
libExpect(tmpEvents).to.include('onConnectionCreated');
|
|
718
|
+
libExpect(tmpEvents).to.include('onFlowChanged');
|
|
719
|
+
fDone();
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
test
|
|
724
|
+
(
|
|
725
|
+
'should return false when no FlowView',
|
|
726
|
+
function (fDone)
|
|
727
|
+
{
|
|
728
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
729
|
+
let tmpResult = tmpManager.addConnection('n1', 'p1', 'n2', 'p2');
|
|
730
|
+
libExpect(tmpResult).to.be.false;
|
|
731
|
+
fDone();
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
// ---- removeConnection ----
|
|
738
|
+
|
|
739
|
+
suite
|
|
740
|
+
(
|
|
741
|
+
'removeConnection',
|
|
742
|
+
function ()
|
|
743
|
+
{
|
|
744
|
+
test
|
|
745
|
+
(
|
|
746
|
+
'should remove a connection by hash',
|
|
747
|
+
function (fDone)
|
|
748
|
+
{
|
|
749
|
+
_MockFlowView._FlowData.Nodes = [
|
|
750
|
+
{ Hash: 'n1', Ports: [{ Hash: 'p1', Direction: 'output' }] },
|
|
751
|
+
{ Hash: 'n2', Ports: [{ Hash: 'p2', Direction: 'input' }] }
|
|
752
|
+
];
|
|
753
|
+
|
|
754
|
+
let tmpConn = _DataManager.addConnection('n1', 'p1', 'n2', 'p2');
|
|
755
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(1);
|
|
756
|
+
|
|
757
|
+
let tmpResult = _DataManager.removeConnection(tmpConn.Hash);
|
|
758
|
+
libExpect(tmpResult).to.be.true;
|
|
759
|
+
libExpect(_MockFlowView._FlowData.Connections).to.have.length(0);
|
|
760
|
+
fDone();
|
|
761
|
+
}
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
test
|
|
765
|
+
(
|
|
766
|
+
'should return false for non-existent connection',
|
|
767
|
+
function (fDone)
|
|
768
|
+
{
|
|
769
|
+
let tmpResult = _DataManager.removeConnection('non-existent');
|
|
770
|
+
libExpect(tmpResult).to.be.false;
|
|
771
|
+
fDone();
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
test
|
|
776
|
+
(
|
|
777
|
+
'should clear selection if removed connection was selected',
|
|
778
|
+
function (fDone)
|
|
779
|
+
{
|
|
780
|
+
_MockFlowView._FlowData.Connections = [
|
|
781
|
+
{ Hash: 'c1', SourceNodeHash: 'n1', TargetNodeHash: 'n2' }
|
|
782
|
+
];
|
|
783
|
+
_MockFlowView._FlowData.ViewState.SelectedConnectionHash = 'c1';
|
|
784
|
+
|
|
785
|
+
_DataManager.removeConnection('c1');
|
|
786
|
+
|
|
787
|
+
libExpect(_MockFlowView._FlowData.ViewState.SelectedConnectionHash).to.be.null;
|
|
788
|
+
fDone();
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
test
|
|
793
|
+
(
|
|
794
|
+
'should fire onConnectionRemoved and onFlowChanged events',
|
|
795
|
+
function (fDone)
|
|
796
|
+
{
|
|
797
|
+
_MockFlowView._FlowData.Connections = [
|
|
798
|
+
{ Hash: 'c1', SourceNodeHash: 'n1', TargetNodeHash: 'n2' }
|
|
799
|
+
];
|
|
800
|
+
let tmpEvents = [];
|
|
801
|
+
_MockFlowView._EventHandlerProvider.fireEvent = function (pEvent) { tmpEvents.push(pEvent); };
|
|
802
|
+
|
|
803
|
+
_DataManager.removeConnection('c1');
|
|
804
|
+
|
|
805
|
+
libExpect(tmpEvents).to.include('onConnectionRemoved');
|
|
806
|
+
libExpect(tmpEvents).to.include('onFlowChanged');
|
|
807
|
+
fDone();
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
test
|
|
812
|
+
(
|
|
813
|
+
'should return false when no FlowView',
|
|
814
|
+
function (fDone)
|
|
815
|
+
{
|
|
816
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
817
|
+
let tmpResult = tmpManager.removeConnection('c1');
|
|
818
|
+
libExpect(tmpResult).to.be.false;
|
|
819
|
+
fDone();
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// ---- marshalToView / marshalFromView ----
|
|
826
|
+
|
|
827
|
+
suite
|
|
828
|
+
(
|
|
829
|
+
'marshalToView and marshalFromView',
|
|
830
|
+
function ()
|
|
831
|
+
{
|
|
832
|
+
test
|
|
833
|
+
(
|
|
834
|
+
'should be no-ops when FlowDataAddress is not set',
|
|
835
|
+
function (fDone)
|
|
836
|
+
{
|
|
837
|
+
// marshalToView with no FlowDataAddress should not crash
|
|
838
|
+
_DataManager.marshalToView();
|
|
839
|
+
// marshalFromView with no FlowDataAddress should not crash
|
|
840
|
+
_DataManager.marshalFromView();
|
|
841
|
+
fDone();
|
|
842
|
+
}
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
test
|
|
846
|
+
(
|
|
847
|
+
'should be no-ops when no FlowView',
|
|
848
|
+
function (fDone)
|
|
849
|
+
{
|
|
850
|
+
let tmpManager = new libDataManager(_Fable, {}, 'NoView');
|
|
851
|
+
tmpManager.marshalToView();
|
|
852
|
+
tmpManager.marshalFromView();
|
|
853
|
+
fDone();
|
|
854
|
+
}
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
);
|