@gregoriusrippenstein/erlang-red-unittest 0.9.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.
@@ -0,0 +1,608 @@
1
+ <script type="text/javascript">
2
+ (function() {
3
+
4
+ function unitTestingPullUpdateFlows(cb = null, token = null) {
5
+ let handleResponse = (resp) => {
6
+ if (resp.status == "failed") {
7
+ return RED.notify(resp.msg, {
8
+ type: "error",
9
+ id: "FlowHubPull",
10
+ timeout: 4000
11
+ });
12
+ }
13
+
14
+ let treeListItems = [];
15
+ let tags = {}
16
+ let nonTag = []
17
+
18
+ Object.keys(resp.data).forEach(function (key) {
19
+ let mth = resp.data[key].name.match(/^(\[.+\])/)
20
+ if (mth) {
21
+ if (tags[mth[0]]) {
22
+ tags[mth[0]].push(resp.data[key])
23
+ } else {
24
+ tags[mth[0]] = [resp.data[key]]
25
+ }
26
+ } else {
27
+ nonTag.push(resp.data[key])
28
+ }
29
+ })
30
+
31
+ Object.keys(tags).sort((a, b) => { return a < b ? -1 : 1 }).forEach(tag => {
32
+ let children = tags[tag].map(itm => {
33
+ return {
34
+ id: `flowid-treelist-${itm.id}`,
35
+ label: itm.name,
36
+ icon: "fa fa-minus",
37
+ flowid: itm.id,
38
+ sublabel: itm.id,
39
+ selected: $('#node-input-flowhubpull-flowid').val() == itm.id,
40
+ checkbox: false,
41
+ children: undefined
42
+ }
43
+ })
44
+
45
+ treeListItems.push({
46
+ label: tag,
47
+ icon: "",
48
+ flowid: "",
49
+ sublabel: "",
50
+ selected: false,
51
+ checkbox: false,
52
+ children: children.sort((a, b) => { return a.label < b.label ? -1 : 1 })
53
+ });
54
+ })
55
+
56
+ nonTag.sort((a, b) => { return a.name < b.name ? -1 : 1 }).forEach(itm => {
57
+ treeListItems.push({
58
+ id: `flowid-treelist-${itm.id}`,
59
+ label: itm.name,
60
+ icon: "fa fa-minus",
61
+ flowid: itm.id,
62
+ sublabel: itm.id,
63
+ selected: $('#node-input-flowhubpull-flowid').val() == itm.id,
64
+ checkbox: false,
65
+ children: undefined
66
+ })
67
+ })
68
+
69
+ if (cb) {
70
+ cb(treeListItems)
71
+ };
72
+ }
73
+
74
+ $.get({
75
+ url: "/UnitTesting/tests.json?cb=" + new Date().getTime(),
76
+ }).done((resp) => {
77
+ handleResponse(resp)
78
+ }).fail((e) => {
79
+ RED.notify("Failed to obtain tests lists", "error")
80
+ });
81
+ };
82
+
83
+
84
+ function doFlowImportForSidebarUnittesting(flowid) {
85
+
86
+ if ( RED.nodes.workspace(flowid) ) {
87
+ RED.workspaces.show(flowid, false, false, true);
88
+ } else {
89
+ RED.notify("Retrieving test", {
90
+ type: "warning",
91
+ timeout: 3000
92
+ });
93
+
94
+ $.get({
95
+ url: "/UnitTesting/" + flowid + "/retrieve?cb=" + new Date().getTime(),
96
+ headers: {
97
+ }
98
+ }).done((e, d) => {
99
+ try {
100
+ if (!e || !e.flowdata || !Array.isArray(e.flowdata)) {
101
+ return RED.notify("Access denied or revision not found.", {
102
+ type: "error",
103
+ timeout: 3000
104
+ });
105
+ }
106
+ } catch (ex) {
107
+ return RED.notify("Access Denied, parse error.", {
108
+ type: "error",
109
+ timeout: 3000
110
+ });
111
+ }
112
+
113
+ RED.clipboard.import();
114
+
115
+ setTimeout(() => {
116
+ var content = e.flowdata;
117
+
118
+
119
+ $('#red-ui-clipboard-dialog-import-text').val(
120
+ JSON.stringify(content)
121
+ ).trigger("paste");
122
+ }, 300);
123
+ }).fail(e => {
124
+ let msg = "<p>Failed to retrieve test flow data from server.</p>" +
125
+ $('#node-input-unittestingpull-flowid').val().trim() + "</p>" +
126
+ "<p>AdBlocker or uBlock might have prevented request.</p>" +
127
+ "<p>Please check browser console for more details.</p>" + e;
128
+
129
+ RED.notify(msg, {
130
+ type: "error",
131
+ id: "UnitTesting",
132
+ timeout: 4000
133
+ });
134
+ });
135
+ }
136
+ }
137
+
138
+ function expandAllTests(dirList) {
139
+ let folders = dirList.treeList('data').filter(d => !d.id);
140
+
141
+ let next = (idx) => {
142
+ if (idx < folders.length) {
143
+ dirList.treeList('select', folders[idx]);
144
+ dirList.treeList('selected').treeList.expand(() => { next(idx + 1) })
145
+ }
146
+ }
147
+
148
+ next(0)
149
+ }
150
+
151
+ function collapseAllTests(dirList) {
152
+ let folders = dirList.treeList('data').filter(d => !d.id);
153
+
154
+ let next = (idx) => {
155
+ if (idx < folders.length) {
156
+ dirList.treeList('select', folders[idx]);
157
+ // for some strange reason, collapse does not have a callback
158
+ dirList.treeList('selected').treeList.collapse()
159
+ next(idx + 1)
160
+ }
161
+ }
162
+
163
+ next(0)
164
+ }
165
+
166
+ function resetTestResultsForFlowId(dirList,flowid) {
167
+ removeTestResultsForFlow(dirList,`flowid-treelist-${flowid}`);
168
+ }
169
+
170
+ function removeTestResultsForFlow(dirList,listid) {
171
+ dirList.treeList('select', listid)
172
+ $($($(dirList.treeList('selected').treeList.container).find('.red-ui-treeList-icon')).find('i')).prop('class', 'fa fa-minus').css('color', '')
173
+ }
174
+
175
+ function removeAllTestResults(dirList) {
176
+ [...dirList.treeList('data').map(d => d.children).flat(),
177
+ ...dirList.treeList('data').filter(d => !!d.id)
178
+ ].forEach(d => {
179
+ removeTestResultsForFlow(dirList,d)
180
+ })
181
+ }
182
+
183
+ function highlightTestResult(dirList, flowid, status) {
184
+ dirList.treeList('select', `flowid-treelist-${flowid}`);
185
+
186
+ let clr = "green"
187
+ let shp = "fa-check"
188
+
189
+ switch (status) {
190
+ case "pending":
191
+ clr = "orange";
192
+ shp = "fa-circle-o";
193
+ break;
194
+ case "failed":
195
+ clr = "red";
196
+ shp = "fa-remove";
197
+ break;
198
+ }
199
+
200
+ $($($(dirList.treeList('selected').treeList.container).find('.red-ui-treeList-icon')).find('i')).prop('class', "fa " + shp).css('color', clr)
201
+ }
202
+
203
+ function clearAllNodeStatus() {
204
+ if (typeof RED.comms.emit !== "undefined") {
205
+ RED.nodes.eachNode((nde) => {
206
+ RED.comms.emit([{
207
+ "topic": `status/${nde.id}`,
208
+ "data": {}
209
+ }])
210
+ });
211
+ }
212
+ }
213
+
214
+ function clearNodeStatusForFlow(flowid) {
215
+ if (typeof RED.comms.emit !== "undefined") {
216
+ RED.nodes.eachNode((nde) => {
217
+ if ( nde.z == flowid) {
218
+ RED.comms.emit([{
219
+ "topic": `status/${nde.id}`,
220
+ "data": {}
221
+ }])
222
+ }
223
+ });
224
+ }
225
+ }
226
+
227
+ function clearDebugPanel() {
228
+ // from [debug node](https://github.com/node-red/node-red/blob/2854351909dee9f92597faba3f37239134294eec/packages/node_modules/%40node-red/nodes/core/common/21-debug.html#L437)
229
+ RED.actions.invoke("core:clear-debug-messages")
230
+ }
231
+
232
+ function runTestForFlowId(flowid,dirList=undefined) {
233
+ if (flowid) {
234
+ if (dirList) {
235
+ resetTestResultsForFlowId(dirList,flowid)
236
+ }
237
+ clearNodeStatusForFlow(flowid)
238
+ resetTestResultsRow();
239
+
240
+ if ( $('#node-input-unittestingpull-clear-debug-on-testrun').is(':checked')) {
241
+ clearDebugPanel()
242
+ }
243
+
244
+ let params = `cb=${new Date().getTime()}`
245
+ if ( $("#node-input-unittestingpull-ignore-pending-status-on-testrun").is(":checked") ) {
246
+ params = `${params}&testpend=true`
247
+ } else {
248
+ params = `${params}&testpend=false`
249
+ }
250
+
251
+ $.get({
252
+ url: `/UnitTesting/${flowid}/runtest?${params}`
253
+ }).done((resp) => {
254
+ RED.notify(`Triggered test run for ${flowid}`, "success")
255
+ $('#node-input-unittestingpull-testresults-row').find(".todo").html(resp.todo)
256
+ }).fail((e) => {
257
+ RED.notify("Failed to trigger test", "error")
258
+ });
259
+ }
260
+ }
261
+
262
+ function testCurrentWorkspace() {
263
+ if (RED.comms.isConnected && !RED.comms.isConnected()) {
264
+ return RED.notify("Not connected to server, websocket down", "error")
265
+ }
266
+ runTestForFlowId(RED.workspaces.active())
267
+ }
268
+
269
+ function sendUnittestngHalt() {
270
+ $.get({
271
+ url: `/UnitTesting/halt?cb=${new Date().getTime()}`
272
+ }).done((resp) => {
273
+ RED.notify(`Halt send successfully`, "success")
274
+ }).fail((e) => {
275
+ RED.notify("Failed to send halt command", "error")
276
+ });
277
+ }
278
+
279
+ function resetTestResultsRow() {
280
+ $('#node-input-unittestingpull-testresults-row').show();
281
+ [".todo", ".pending", ".total", ".success",".failed"].forEach( d => {
282
+ $('#node-input-unittestingpull-testresults-row').find(d).html("0")
283
+ })
284
+ }
285
+ function runAllTests(dirList) {
286
+ if (RED.comms.isConnected && !RED.comms.isConnected()) {
287
+ return RED.notify("Not connected to server, websocket down", "error")
288
+ }
289
+
290
+ resetTestResultsRow();
291
+ expandAllTests(dirList)
292
+ clearAllNodeStatus()
293
+
294
+ setTimeout(() => {
295
+ removeAllTestResults(dirList);
296
+
297
+ if ( $('#node-input-unittestingpull-clear-debug-on-testrun').is(':checked')) {
298
+ clearDebugPanel()
299
+ }
300
+
301
+ let params = `cb=${new Date().getTime()}`
302
+ if ($("#node-input-unittestingpull-ignore-pending-status-on-testrun").is(":checked")) {
303
+ params = `${params}&testpend=true`
304
+ } else {
305
+ params = `${params}&testpend=false`
306
+ }
307
+
308
+ $.get({
309
+ url: `/UnitTesting/all/runtest?${params}`
310
+ }).done((resp) => {
311
+ RED.notify(`${resp.todo} tests have been triggered`, "success")
312
+ $('#node-input-unittestingpull-testresults-row').find(".todo").html(resp.todo)
313
+ }).fail((e) => {
314
+ RED.notify("Failed to trigger tests", "error")
315
+ });
316
+ }, 200)
317
+ }
318
+
319
+ // Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
320
+ var initialiseConfigNodeOnce = () => {
321
+ RED.events.off('runtime-state', initialiseConfigNodeOnce);
322
+
323
+ var content = $($('script[type="text/x-red"][data-template-name="UnitTestingSidebar"]').i18n().html());
324
+
325
+ // Add a "Your sidebar" tabsheet to the right sidebar panel, in which this sidebar panel can be displayed
326
+ // --> more details: https://nodered.org/docs/api/ui/sidebar/
327
+
328
+ RED.sidebar.addTab({
329
+ id: "UnitTestingErlangRed",
330
+ label: "Testing", // short name for the tab
331
+ name: "Testing", // long name for the menu
332
+ content: content,
333
+ enableOnEdit: true,
334
+ iconClass: "fa fa-th",
335
+ visible: true
336
+ });
337
+
338
+
339
+ var tabs = RED.tabs.create({
340
+ id: 'func-unittesting-tabs',
341
+ onchange: function (tab) {
342
+ $('#func-unittesting-tabs-content').children().hide();
343
+ $('#' + tab.id).show();
344
+ }
345
+ });
346
+
347
+ // Add pull tab
348
+ tabs.addTab({
349
+ id: 'func-unittesting-tab-pull',
350
+ iconClass: 'fa fa-arrow-down',
351
+ label: 'Tests'
352
+ });
353
+
354
+ // Add compare tab
355
+ tabs.addTab({
356
+ id: 'func-unittesting-tab-compare',
357
+ iconClass: 'fa fa-clone',
358
+ label: 'TBD'
359
+ });
360
+
361
+ // Add push tab
362
+ tabs.addTab({
363
+ id: 'func-unittesting-tab-push',
364
+ iconClass: 'fa fa-arrow-up',
365
+ label: 'TBD'
366
+ });
367
+
368
+ tabs.activateTab("func-unittesting-tab-pull");
369
+
370
+ var dirList = $("#node-input-unittestingpull-sb-target-container-div").css({
371
+ width: "100%",
372
+ height: "100%"
373
+ }).treeList(
374
+ {
375
+ multi: false
376
+ }
377
+ ).on('treelistselect', function (event, item) {
378
+ if ( item.flowid) {
379
+ $('#unittesting-view-testflow-link').prop('href', `?tstid=${item.flowid}`)
380
+ $('#unittesting-view-testflow-link').fadeIn(300)
381
+ } else {
382
+ $('#unittesting-view-testflow-link').fadeOut(300)
383
+ $('#unittesting-view-testflow-link').prop('href', "")
384
+ }
385
+ }).on('treelistconfirm', function (event, item) {
386
+ if (item.flowid) {
387
+ doFlowImportForSidebarUnittesting(item.flowid)
388
+ }
389
+ });
390
+
391
+
392
+ var search = $("#node-input-unittestingpull-sb-target-filter").searchBox({
393
+ style: "compact",
394
+ delay: 300,
395
+ change: function () {
396
+ var val = $(this).val().trim().toLowerCase();
397
+ if (val === "") {
398
+ dirList.treeList("filter", null);
399
+ search.searchBox("count", "");
400
+ } else {
401
+ var count = dirList.treeList("filter", function (item) {
402
+ return item.label.toLowerCase().indexOf(val) > -1 || item.sublabel.toLowerCase().indexOf(val) > -1
403
+ });
404
+ search.searchBox("count", count + " / " + dirList.treeList("data").length);
405
+ }
406
+ }
407
+ });
408
+
409
+ $('#node-input-unittestingpull-sb-test-flow-but').on('click', function (e) {
410
+ e.preventDefault();
411
+
412
+ if (RED.comms.isConnected && !RED.comms.isConnected()) {
413
+ return RED.notify("Not connected to server, websocket down", "error")
414
+ }
415
+
416
+ let sltd = dirList.treeList('selected')
417
+ if ( sltd.flowid) {
418
+ runTestForFlowId(sltd.flowid,dirList)
419
+ } else if (sltd.children) {
420
+ sltd.children.map( d => d.flowid ).map( d => runTestForFlowId(d,dirList))
421
+ }
422
+ })
423
+
424
+ $('#node-input-unittestingpull-sb-testall-but').on('click', function (e) {
425
+ e.preventDefault();
426
+ runAllTests(dirList)
427
+ })
428
+
429
+ let handleTestResult = (event, data) => {
430
+ if (data.flowid) {
431
+ if ( data.status == "unknown_testcase") {
432
+ RED.notify("Test with flowid " + data.flowid + ": is unknown to us", {
433
+ type: "warning",
434
+ timeout: 3000
435
+ });
436
+ return
437
+ }
438
+
439
+ RED.notify("Test result for flowid " + data.flowid + ": " + data.status, {
440
+ type: data.status == "failed" ? "error" : (data.status == "pending" ? "warning" : "success"),
441
+ timeout: (data.status == "failed" || data.status == "pending") ? 3000 : 2000
442
+ });
443
+
444
+ // update the test results row
445
+ let v = parseInt($("#node-input-unittestingpull-testresults-row").find(".todo").html());
446
+ if ( v > 0 ) {
447
+ $("#node-input-unittestingpull-testresults-row").find(".todo").html(`${v - 1}`)
448
+ }
449
+ v = parseInt($("#node-input-unittestingpull-testresults-row").find("."+data.status).html());
450
+ $("#node-input-unittestingpull-testresults-row").find("." + data.status).html(`${v+1}`)
451
+ v = parseInt($("#node-input-unittestingpull-testresults-row").find(".total").html());
452
+ $("#node-input-unittestingpull-testresults-row").find(".total").html(`${v + 1}`)
453
+
454
+ highlightTestResult(dirList, data.flowid, data.status);
455
+ }
456
+ }
457
+ RED.comms.subscribe('unittesting:testresults', handleTestResult);
458
+
459
+ $('#node-input-unittestingpull-expand-all-but').on('click', function (e) {
460
+ e.preventDefault();
461
+ expandAllTests(dirList)
462
+ })
463
+
464
+ $('#node-input-unittestingpull-collapse-but').on('click', function (e) {
465
+ e.preventDefault();
466
+ collapseAllTests(dirList)
467
+ })
468
+
469
+ $('#node-input-unittestingpull-clear-debug-panel-but').on('click', function (e) {
470
+ e.preventDefault();
471
+ clearDebugPanel()
472
+ })
473
+
474
+ $('#node-input-unittestingpull-sb-refresh-list-but').on('click', function (e) {
475
+ e.preventDefault();
476
+
477
+ setTimeout(() => {
478
+ unitTestingPullUpdateFlows((lst) => {
479
+ dirList.treeList('empty')
480
+ $('#unittestingpull-sb-view-flow-link').hide()
481
+ setTimeout(() => {
482
+ dirList.treeList('data', lst)
483
+ }, 150)
484
+ }, undefined);
485
+ }, 150);
486
+ });
487
+
488
+ // debugging
489
+ window.unitTestDirList = dirList;
490
+ window.fakeTestResult = handleTestResult;
491
+
492
+ RED.comms.on("connect", () => {
493
+ $('#node-input-unittestingpull-sb-testall-but').prop('disabled', false)
494
+ $('#node-input-unittestingpull-sb-test-flow-but').prop('disabled', false)
495
+ });
496
+
497
+ RED.comms.on("disconnect", () => {
498
+ $('#node-input-unittestingpull-sb-testall-but').prop('disabled', true)
499
+ $('#node-input-unittestingpull-sb-test-flow-but').prop('disabled', true)
500
+ });
501
+
502
+ if ( !RED.comms.isConnected || RED.comms.isConnected() ) {
503
+ $('#node-input-unittestingpull-sb-testall-but').prop('disabled', false)
504
+ $('#node-input-unittestingpull-sb-test-flow-but').prop('disabled', false)
505
+ }
506
+
507
+ RED.actions.add("unittesting:send-halt-to-test-server", sendUnittestngHalt)
508
+ RED.actions.add("unittesting:test-current-workspace", testCurrentWorkspace)
509
+ RED.actions.add("unittesting:run-all-tests", () => { runAllTests(dirList) } )
510
+
511
+ };
512
+
513
+ RED.events.on('runtime-state', initialiseConfigNodeOnce);
514
+ })();
515
+ </script>
516
+
517
+ <!-- The html for the right sidebar plugin screen -->
518
+ <script type="text/x-red" data-template-name="UnitTestingSidebar">
519
+ <div class="form-row func-unittesting-tabs-row">
520
+ <ul style="min-width: 600px; margin-bottom: 20px;" id="func-unittesting-tabs"></ul>
521
+ </div>
522
+
523
+ <!--func-unittesting-tabs-row-->
524
+ <div id="func-unittesting-tabs-content">
525
+
526
+ <!--func-unittesting-tab-pull#-->
527
+ <div id="func-unittesting-tab-pull" style="display:none" style="min-height: calc(100% - 95px);">
528
+ <div class="form-row">
529
+ <div class="col-100">
530
+
531
+ <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
532
+ <button id="node-input-unittestingpull-sb-refresh-list-but"
533
+ class="red-ui-button"><i class="fa fa-refresh"></i> Refresh Test List</button>
534
+ <a id="unittesting-view-testflow-link"
535
+ style="color: blue; display: none; margin-left: 20px;"
536
+ target="_blank" href="">Share Testcase <i class="fa fa-external-link"></i></a>
537
+ </div>
538
+
539
+ <div class="form-row"
540
+ style="position: relative; min-height: 100px; height: 500px; margin-left: 10px; margin-right: 15px; margin-top: 5px;">
541
+ <div style="position: absolute; top: -30px; right: 0px;">
542
+ <input type="text" id="node-input-unittestingpull-sb-target-filter" placeholder="Name or Flow Id">
543
+ </div>
544
+
545
+ <div id="node-input-unittestingpull-sb-target-container-div" style="min-height: 500px;"></div>
546
+ </div>
547
+ </div>
548
+ </div>
549
+
550
+ <div id="node-input-unittestingpull-testresults-row" class="form-row"
551
+ style="margin-left: 10px; margin-top: 30px; display: none;">
552
+ Todo <span class="todo" style="color: purple">0</span>
553
+ / Succeed <span class="success" style="color: green">0</span>
554
+ / Pending <span class="pending" style="color: orange">0</span>
555
+ / Failed <span class="failed" style="color: red">0</span>
556
+ / Done <span class='total'>0</span>
557
+ </div>
558
+
559
+ <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
560
+ <button id="node-input-unittestingpull-sb-testall-but" disabled=disabled
561
+ class="red-ui-button">Test all</button>
562
+ <button id="node-input-unittestingpull-sb-test-flow-but" disabled=disabled
563
+ class="red-ui-button">Test one</button>
564
+ </div>
565
+
566
+ <div class="form-row" style="margin-left: 10px;">
567
+ <input type="checkbox" id="node-input-unittestingpull-clear-debug-on-testrun"
568
+ style="display:inline-block; margin-left: 10px; width:15px; vertical-align:baseline;">
569
+ <label for="node-input-unittestingpull-clear-debug-on-testrun" style="width: 250px;">
570
+ <span>Clear debug panel on every test run?</span>
571
+ </label>
572
+ </div>
573
+
574
+ <div class="form-row" style="margin-left: 10px;">
575
+ <input type="checkbox" id="node-input-unittestingpull-ignore-pending-status-on-testrun"
576
+ style="display:inline-block; margin-left: 10px; width:15px; vertical-align:baseline;">
577
+ <label for="node-input-unittestingpull-ignore-pending-status-on-testrun" style="width: 250px;">
578
+ <span>Test pending tests also?</span>
579
+ </label>
580
+ </div>
581
+
582
+ <div class="form-row" style="margin-left: 10px;">
583
+ <button id="node-input-unittestingpull-expand-all-but"
584
+ class="red-ui-button">Expand</button>
585
+ <button id="node-input-unittestingpull-collapse-but"
586
+ class="red-ui-button">Collapse</button>
587
+ <button id="node-input-unittestingpull-clear-debug-panel-but"
588
+ class="red-ui-button">Clear Debug Panel</button>
589
+ </div>
590
+ </div>
591
+
592
+ <!--func-unittesting-tab-compare#-->
593
+ <div id="func-unittesting-tab-compare" style="display:none; height: 87vh;">
594
+ <div class="form-row" style="margin-left: 10px;">
595
+ This tab is intentionally left blank.
596
+ </div>
597
+ </div>
598
+
599
+ <!--func-unittesting-tab-push#-->
600
+ <div id="func-unittesting-tab-push" style="display:none">
601
+ <div class="form-row" style="margin-left: 10px;">
602
+ This tab is intentionally left blank.
603
+ </div>
604
+ </div>
605
+ <!--func-unittesting-tab-tab#-->
606
+ </div>
607
+ <!--func-unittesting-tabs-content-->
608
+ </script>