@nyaruka/temba-components 0.138.6 → 0.140.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.
Files changed (196) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/CHANGELOG.md +26 -0
  4. package/demo/data/flows/sample-flow.json +24 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +1112 -882
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +10 -7
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/Dropdown.js +3 -1
  18. package/out-tsc/src/display/Dropdown.js.map +1 -1
  19. package/out-tsc/src/display/FloatingTab.js +25 -32
  20. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  21. package/out-tsc/src/display/Thumbnail.js +163 -5
  22. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  23. package/out-tsc/src/flow/CanvasMenu.js +5 -3
  24. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  25. package/out-tsc/src/flow/CanvasNode.js +70 -29
  26. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  27. package/out-tsc/src/flow/Editor.js +290 -239
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/NodeEditor.js +118 -10
  30. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  31. package/out-tsc/src/flow/Plumber.js +757 -403
  32. package/out-tsc/src/flow/Plumber.js.map +1 -1
  33. package/out-tsc/src/flow/StickyNote.js +13 -4
  34. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  35. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  36. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  37. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  38. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  39. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  40. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  41. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  42. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  43. package/out-tsc/src/flow/config.js +11 -3
  44. package/out-tsc/src/flow/config.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  46. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  48. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  49. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  50. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  51. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  52. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  53. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  54. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  55. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  56. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  57. package/out-tsc/src/flow/operators.js +21 -5
  58. package/out-tsc/src/flow/operators.js.map +1 -1
  59. package/out-tsc/src/flow/types.js.map +1 -1
  60. package/out-tsc/src/flow/utils.js +213 -65
  61. package/out-tsc/src/flow/utils.js.map +1 -1
  62. package/out-tsc/src/form/ArrayEditor.js +4 -2
  63. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  64. package/out-tsc/src/form/FieldRenderer.js +49 -0
  65. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  66. package/out-tsc/src/interfaces.js +2 -0
  67. package/out-tsc/src/interfaces.js.map +1 -1
  68. package/out-tsc/src/layout/Dialog.js +52 -7
  69. package/out-tsc/src/layout/Dialog.js.map +1 -1
  70. package/out-tsc/src/list/TicketList.js +4 -1
  71. package/out-tsc/src/list/TicketList.js.map +1 -1
  72. package/out-tsc/src/live/TembaChart.js.map +1 -1
  73. package/out-tsc/src/locales/es.js +5 -5
  74. package/out-tsc/src/locales/es.js.map +1 -1
  75. package/out-tsc/src/locales/fr.js +5 -5
  76. package/out-tsc/src/locales/fr.js.map +1 -1
  77. package/out-tsc/src/locales/locale-codes.js +2 -11
  78. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  79. package/out-tsc/src/locales/pt.js +5 -5
  80. package/out-tsc/src/locales/pt.js.map +1 -1
  81. package/out-tsc/src/simulator/Simulator.js +10 -3
  82. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  83. package/out-tsc/src/store/AppState.js +89 -3
  84. package/out-tsc/src/store/AppState.js.map +1 -1
  85. package/out-tsc/test/actions/play_audio.test.js +118 -0
  86. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  87. package/out-tsc/test/actions/say_msg.test.js +158 -0
  88. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  89. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  90. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  91. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  92. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  93. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  94. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  95. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  96. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  97. package/out-tsc/test/temba-floating-tab.test.js +4 -6
  98. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  99. package/out-tsc/test/temba-flow-collision.test.js +473 -220
  100. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  101. package/out-tsc/test/temba-flow-editor.test.js +0 -2
  102. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  103. package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
  104. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  105. package/out-tsc/test/temba-flow-plumber.test.js +102 -93
  106. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  107. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  108. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  109. package/package.json +1 -1
  110. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  111. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  112. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  113. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  114. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  115. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  116. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  117. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  118. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  119. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  120. package/screenshots/truth/editor/router.png +0 -0
  121. package/screenshots/truth/editor/wait.png +0 -0
  122. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  123. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  124. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  125. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  134. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  135. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  136. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  142. package/src/display/Chat.ts +13 -7
  143. package/src/display/Dropdown.ts +3 -1
  144. package/src/display/FloatingTab.ts +24 -33
  145. package/src/display/Thumbnail.ts +162 -2
  146. package/src/flow/CanvasMenu.ts +8 -3
  147. package/src/flow/CanvasNode.ts +75 -30
  148. package/src/flow/Editor.ts +336 -288
  149. package/src/flow/NodeEditor.ts +137 -9
  150. package/src/flow/Plumber.ts +1011 -457
  151. package/src/flow/StickyNote.ts +14 -4
  152. package/src/flow/actions/audio-player.ts +127 -0
  153. package/src/flow/actions/enter_flow.ts +44 -0
  154. package/src/flow/actions/play_audio.ts +64 -5
  155. package/src/flow/actions/say_msg.ts +94 -4
  156. package/src/flow/config.ts +11 -3
  157. package/src/flow/nodes/shared-rules.ts +1 -1
  158. package/src/flow/nodes/terminal.ts +9 -0
  159. package/src/flow/nodes/wait_for_audio.ts +88 -0
  160. package/src/flow/nodes/wait_for_dial.ts +176 -0
  161. package/src/flow/nodes/wait_for_digits.ts +86 -2
  162. package/src/flow/nodes/wait_for_menu.ts +209 -3
  163. package/src/flow/operators.ts +23 -5
  164. package/src/flow/types.ts +23 -1
  165. package/src/flow/utils.ts +238 -81
  166. package/src/form/ArrayEditor.ts +4 -2
  167. package/src/form/FieldRenderer.ts +64 -1
  168. package/src/interfaces.ts +3 -1
  169. package/src/layout/Dialog.ts +53 -7
  170. package/src/list/TicketList.ts +4 -1
  171. package/src/live/TembaChart.ts +1 -1
  172. package/src/locales/es.ts +13 -18
  173. package/src/locales/fr.ts +13 -18
  174. package/src/locales/locale-codes.ts +2 -11
  175. package/src/locales/pt.ts +13 -18
  176. package/src/simulator/Simulator.ts +13 -3
  177. package/src/store/AppState.ts +105 -1
  178. package/src/store/flow-definition.d.ts +2 -0
  179. package/test/actions/play_audio.test.ts +155 -0
  180. package/test/actions/say_msg.test.ts +196 -0
  181. package/test/nodes/wait_for_audio.test.ts +182 -0
  182. package/test/nodes/wait_for_dial.test.ts +382 -0
  183. package/test/nodes/wait_for_digits.test.ts +233 -109
  184. package/test/nodes/wait_for_menu.test.ts +383 -0
  185. package/test/temba-floating-tab.test.ts +4 -6
  186. package/test/temba-flow-collision.test.ts +495 -293
  187. package/test/temba-flow-editor.test.ts +0 -2
  188. package/test/temba-flow-plumber-connections.test.ts +97 -97
  189. package/test/temba-flow-plumber.test.ts +116 -103
  190. package/test/temba-node-type-selector.test.ts +6 -6
  191. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -6,115 +6,114 @@ describe('Plumber - Connection Management', () => {
6
6
  let mockCanvas;
7
7
  let clock;
8
8
  beforeEach(() => {
9
- // Use fake timers to control setTimeout
10
9
  clock = useFakeTimers();
11
- // Create mock canvas and make getElementById return a mock element
12
10
  mockCanvas = document.createElement('div');
13
- const mockElement = document.createElement('div');
14
- stub(document, 'getElementById').returns(mockElement);
15
- // Create a mock editor with fireCustomEvent
11
+ mockCanvas.id = 'canvas';
12
+ document.body.appendChild(mockCanvas);
16
13
  const mockEditor = { fireCustomEvent: stub() };
17
- // Create a new plumber instance
18
14
  plumber = new Plumber(mockCanvas, mockEditor);
19
- // Replace the internal jsPlumb instance with mocks
20
- plumber.jsPlumb = {
21
- getConnections: stub().returns([]),
22
- addClass: stub(),
23
- removeClass: stub(),
24
- batch: stub().callsFake((fn) => fn()),
25
- addEndpoint: stub().returns({}),
26
- revalidate: stub(),
27
- connect: stub(),
28
- getEndpoints: stub().returns([
29
- { elementId: 'test-from', addClass: stub() }
30
- ]),
31
- select: stub().returns({
32
- deleteAll: stub()
33
- }),
34
- selectEndpoints: stub().returns({
35
- deleteAll: stub()
36
- }),
37
- deleteConnection: stub(),
38
- removeAllEndpoints: stub(),
39
- repaintEverything: stub()
40
- };
41
15
  });
42
16
  afterEach(() => {
43
- var _a, _b;
44
- // Restore the original document.getElementById
45
- (_b = (_a = document.getElementById).restore) === null || _b === void 0 ? void 0 : _b.call(_a);
17
+ mockCanvas.remove();
46
18
  clock.restore();
47
19
  });
48
20
  describe('setConnectionRemovingState', () => {
49
- it('returns false when no connections are found', () => {
50
- const result = plumber.setConnectionRemovingState('test-exit', true);
21
+ it('returns false when no connection exists for the exit', () => {
22
+ const result = plumber.setConnectionRemovingState('nonexistent', true);
51
23
  expect(result).to.be.false;
52
- expect(plumber.jsPlumb.getConnections).to.have.been.called;
53
24
  });
54
- it('sets removing class on connections when isRemoving is true', () => {
55
- const mockConnections = [
56
- { id: 'conn1', addClass: stub() },
57
- { id: 'conn2', addClass: stub() }
58
- ];
59
- plumber.jsPlumb.getConnections = stub().returns(mockConnections);
60
- const result = plumber.setConnectionRemovingState('test-exit', true);
25
+ it('adds removing class when isRemoving is true', () => {
26
+ // Create mock elements for a connection
27
+ const exitEl = document.createElement('div');
28
+ exitEl.id = 'exit-1';
29
+ const targetEl = document.createElement('div');
30
+ targetEl.id = 'target-1';
31
+ mockCanvas.appendChild(exitEl);
32
+ mockCanvas.appendChild(targetEl);
33
+ // Create a connection
34
+ plumber.connectIds('node-1', 'exit-1', 'target-1');
35
+ clock.tick(16);
36
+ const result = plumber.setConnectionRemovingState('exit-1', true);
61
37
  expect(result).to.be.true;
62
- expect(mockConnections[0].addClass).to.have.been.calledWith('removing');
63
- expect(mockConnections[1].addClass).to.have.been.calledWith('removing');
38
+ const conn = plumber.connections.get('exit-1');
39
+ expect(conn.svgEl.classList.contains('removing')).to.be.true;
40
+ exitEl.remove();
41
+ targetEl.remove();
64
42
  });
65
- it('removes removing class from connections when isRemoving is false', () => {
66
- const mockConnections = [
67
- { id: 'conn1', removeClass: stub() },
68
- { id: 'conn2', removeClass: stub() }
69
- ];
70
- plumber.jsPlumb.getConnections = stub().returns(mockConnections);
71
- const result = plumber.setConnectionRemovingState('test-exit', false);
43
+ it('removes removing class when isRemoving is false', () => {
44
+ const exitEl = document.createElement('div');
45
+ exitEl.id = 'exit-2';
46
+ const targetEl = document.createElement('div');
47
+ targetEl.id = 'target-2';
48
+ mockCanvas.appendChild(exitEl);
49
+ mockCanvas.appendChild(targetEl);
50
+ plumber.connectIds('node-1', 'exit-2', 'target-2');
51
+ clock.tick(16);
52
+ plumber.setConnectionRemovingState('exit-2', true);
53
+ plumber.setConnectionRemovingState('exit-2', false);
54
+ const conn = plumber.connections.get('exit-2');
55
+ expect(conn.svgEl.classList.contains('removing')).to.be.false;
56
+ exitEl.remove();
57
+ targetEl.remove();
58
+ });
59
+ });
60
+ describe('removeExitConnection', () => {
61
+ it('removes a connection for an exit', () => {
62
+ const exitEl = document.createElement('div');
63
+ exitEl.id = 'exit-3';
64
+ const targetEl = document.createElement('div');
65
+ targetEl.id = 'target-3';
66
+ mockCanvas.appendChild(exitEl);
67
+ mockCanvas.appendChild(targetEl);
68
+ plumber.connectIds('node-1', 'exit-3', 'target-3');
69
+ clock.tick(16);
70
+ expect(plumber.connections.has('exit-3')).to.be.true;
71
+ const result = plumber.removeExitConnection('exit-3');
72
72
  expect(result).to.be.true;
73
- expect(mockConnections[0].removeClass).to.have.been.calledWith('removing');
74
- expect(mockConnections[1].removeClass).to.have.been.calledWith('removing');
73
+ expect(plumber.connections.has('exit-3')).to.be.false;
74
+ exitEl.remove();
75
+ targetEl.remove();
76
+ });
77
+ it('returns false when no connection exists', () => {
78
+ const result = plumber.removeExitConnection('nonexistent');
79
+ expect(result).to.be.false;
75
80
  });
76
81
  });
77
82
  describe('connectIds and processPendingConnections', () => {
78
83
  it('adds connection to pending connections', () => {
79
- // Call connectIds which should add to pending connections
80
84
  plumber.connectIds('test-node', 'test-from', 'test-to');
81
- // Verify pendingConnections has the new connection
82
85
  expect(plumber.pendingConnections.length).to.equal(1);
83
- // Advance timer to trigger the timeout
84
- clock.tick(51); // Just past the 50ms timeout
85
- // Now the batch should have been called
86
- expect(plumber.jsPlumb.batch).to.have.been.called;
87
- expect(plumber.jsPlumb.addEndpoint).to.have.been.called;
88
- expect(plumber.jsPlumb.connect).to.have.been.called;
89
86
  });
90
- it('clears previous timeout when called multiple times', () => {
91
- // Set up spies for window.setTimeout and window.clearTimeout instead of global
92
- const clearTimeoutSpy = stub(window, 'clearTimeout');
93
- const setTimeoutSpy = stub(window, 'setTimeout').returns(123);
94
- // Call twice
87
+ it('clears previous rAF when called multiple times', () => {
88
+ const cancelSpy = stub(window, 'cancelAnimationFrame');
89
+ const rafSpy = stub(window, 'requestAnimationFrame').returns(123);
95
90
  plumber.processPendingConnections();
96
91
  plumber.processPendingConnections();
97
- // Should have called clearTimeout once and setTimeout twice
98
- expect(clearTimeoutSpy).to.have.been.calledOnce;
99
- expect(setTimeoutSpy).to.have.been.calledTwice;
100
- // Clean up
101
- clearTimeoutSpy.restore();
102
- setTimeoutSpy.restore();
92
+ expect(cancelSpy).to.have.been.calledOnce;
93
+ expect(rafSpy).to.have.been.calledTwice;
94
+ cancelSpy.restore();
95
+ rafSpy.restore();
103
96
  });
104
97
  });
105
- describe('removeExitConnection', () => {
106
- it('removes connections for an exit', () => {
107
- const mockConnections = [{ id: 'conn1' }, { id: 'conn2' }];
108
- plumber.jsPlumb.getConnections = stub().returns(mockConnections);
109
- const result = plumber.removeExitConnection('test-exit');
110
- expect(result).to.be.true;
111
- expect(plumber.jsPlumb.deleteConnection).to.have.been
112
- .calledTwice;
113
- });
114
- it('returns false when no connections exist', () => {
115
- plumber.jsPlumb.getConnections = stub().returns([]);
116
- const result = plumber.removeExitConnection('test-exit');
117
- expect(result).to.be.false;
98
+ describe('removeNodeConnections', () => {
99
+ it('removes inbound and outbound connections for a node', () => {
100
+ const exitEl = document.createElement('div');
101
+ exitEl.id = 'exit-4';
102
+ exitEl.classList.add('exit');
103
+ const nodeEl = document.createElement('div');
104
+ nodeEl.id = 'node-1';
105
+ nodeEl.appendChild(exitEl);
106
+ const targetEl = document.createElement('div');
107
+ targetEl.id = 'target-4';
108
+ mockCanvas.appendChild(nodeEl);
109
+ mockCanvas.appendChild(targetEl);
110
+ plumber.connectIds('node-1', 'exit-4', 'target-4');
111
+ clock.tick(16);
112
+ expect(plumber.connections.size).to.equal(1);
113
+ plumber.removeNodeConnections('node-1', ['exit-4']);
114
+ expect(plumber.connections.size).to.equal(0);
115
+ nodeEl.remove();
116
+ targetEl.remove();
118
117
  });
119
118
  });
120
119
  });
@@ -1 +1 @@
1
- {"version":3,"file":"temba-flow-plumber-connections.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber-connections.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\nimport { Plumber } from '../src/flow/Plumber';\n\ndescribe('Plumber - Connection Management', () => {\n let plumber: Plumber;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n\n beforeEach(() => {\n // Use fake timers to control setTimeout\n clock = useFakeTimers();\n\n // Create mock canvas and make getElementById return a mock element\n mockCanvas = document.createElement('div');\n const mockElement = document.createElement('div');\n stub(document, 'getElementById').returns(mockElement);\n\n // Create a mock editor with fireCustomEvent\n const mockEditor = { fireCustomEvent: stub() };\n\n // Create a new plumber instance\n plumber = new Plumber(mockCanvas, mockEditor);\n\n // Replace the internal jsPlumb instance with mocks\n (plumber as any).jsPlumb = {\n getConnections: stub().returns([]),\n addClass: stub(),\n removeClass: stub(),\n batch: stub().callsFake((fn: any) => fn()),\n addEndpoint: stub().returns({}),\n revalidate: stub(),\n connect: stub(),\n getEndpoints: stub().returns([\n { elementId: 'test-from', addClass: stub() }\n ]),\n select: stub().returns({\n deleteAll: stub()\n }),\n selectEndpoints: stub().returns({\n deleteAll: stub()\n }),\n deleteConnection: stub(),\n removeAllEndpoints: stub(),\n repaintEverything: stub()\n };\n });\n\n afterEach(() => {\n // Restore the original document.getElementById\n (document.getElementById as any).restore?.();\n clock.restore();\n });\n\n describe('setConnectionRemovingState', () => {\n it('returns false when no connections are found', () => {\n const result = plumber.setConnectionRemovingState('test-exit', true);\n expect(result).to.be.false;\n expect((plumber as any).jsPlumb.getConnections).to.have.been.called;\n });\n\n it('sets removing class on connections when isRemoving is true', () => {\n const mockConnections = [\n { id: 'conn1', addClass: stub() },\n { id: 'conn2', addClass: stub() }\n ];\n\n (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);\n\n const result = plumber.setConnectionRemovingState('test-exit', true);\n expect(result).to.be.true;\n expect(mockConnections[0].addClass).to.have.been.calledWith('removing');\n expect(mockConnections[1].addClass).to.have.been.calledWith('removing');\n });\n\n it('removes removing class from connections when isRemoving is false', () => {\n const mockConnections = [\n { id: 'conn1', removeClass: stub() },\n { id: 'conn2', removeClass: stub() }\n ];\n\n (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);\n\n const result = plumber.setConnectionRemovingState('test-exit', false);\n expect(result).to.be.true;\n expect(mockConnections[0].removeClass).to.have.been.calledWith(\n 'removing'\n );\n expect(mockConnections[1].removeClass).to.have.been.calledWith(\n 'removing'\n );\n });\n });\n\n describe('connectIds and processPendingConnections', () => {\n it('adds connection to pending connections', () => {\n // Call connectIds which should add to pending connections\n plumber.connectIds('test-node', 'test-from', 'test-to');\n\n // Verify pendingConnections has the new connection\n expect((plumber as any).pendingConnections.length).to.equal(1);\n\n // Advance timer to trigger the timeout\n clock.tick(51); // Just past the 50ms timeout\n\n // Now the batch should have been called\n expect((plumber as any).jsPlumb.batch).to.have.been.called;\n expect((plumber as any).jsPlumb.addEndpoint).to.have.been.called;\n expect((plumber as any).jsPlumb.connect).to.have.been.called;\n });\n\n it('clears previous timeout when called multiple times', () => {\n // Set up spies for window.setTimeout and window.clearTimeout instead of global\n const clearTimeoutSpy = stub(window, 'clearTimeout');\n const setTimeoutSpy = stub(window, 'setTimeout').returns(123 as any);\n\n // Call twice\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n // Should have called clearTimeout once and setTimeout twice\n expect(clearTimeoutSpy).to.have.been.calledOnce;\n expect(setTimeoutSpy).to.have.been.calledTwice;\n\n // Clean up\n clearTimeoutSpy.restore();\n setTimeoutSpy.restore();\n });\n });\n\n describe('removeExitConnection', () => {\n it('removes connections for an exit', () => {\n const mockConnections = [{ id: 'conn1' }, { id: 'conn2' }];\n (plumber as any).jsPlumb.getConnections = stub().returns(mockConnections);\n\n const result = plumber.removeExitConnection('test-exit');\n\n expect(result).to.be.true;\n expect((plumber as any).jsPlumb.deleteConnection).to.have.been\n .calledTwice;\n });\n\n it('returns false when no connections exist', () => {\n (plumber as any).jsPlumb.getConnections = stub().returns([]);\n\n const result = plumber.removeExitConnection('test-exit');\n\n expect(result).to.be.false;\n });\n });\n});\n"]}
1
+ {"version":3,"file":"temba-flow-plumber-connections.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber-connections.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\nimport { Plumber } from '../src/flow/Plumber';\n\ndescribe('Plumber - Connection Management', () => {\n let plumber: Plumber;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n\n beforeEach(() => {\n clock = useFakeTimers();\n\n mockCanvas = document.createElement('div');\n mockCanvas.id = 'canvas';\n document.body.appendChild(mockCanvas);\n\n const mockEditor = { fireCustomEvent: stub() };\n plumber = new Plumber(mockCanvas, mockEditor);\n });\n\n afterEach(() => {\n mockCanvas.remove();\n clock.restore();\n });\n\n describe('setConnectionRemovingState', () => {\n it('returns false when no connection exists for the exit', () => {\n const result = plumber.setConnectionRemovingState('nonexistent', true);\n expect(result).to.be.false;\n });\n\n it('adds removing class when isRemoving is true', () => {\n // Create mock elements for a connection\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-1';\n const targetEl = document.createElement('div');\n targetEl.id = 'target-1';\n mockCanvas.appendChild(exitEl);\n mockCanvas.appendChild(targetEl);\n\n // Create a connection\n plumber.connectIds('node-1', 'exit-1', 'target-1');\n clock.tick(16);\n\n const result = plumber.setConnectionRemovingState('exit-1', true);\n expect(result).to.be.true;\n\n const conn = (plumber as any).connections.get('exit-1');\n expect(conn.svgEl.classList.contains('removing')).to.be.true;\n\n exitEl.remove();\n targetEl.remove();\n });\n\n it('removes removing class when isRemoving is false', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-2';\n const targetEl = document.createElement('div');\n targetEl.id = 'target-2';\n mockCanvas.appendChild(exitEl);\n mockCanvas.appendChild(targetEl);\n\n plumber.connectIds('node-1', 'exit-2', 'target-2');\n clock.tick(16);\n\n plumber.setConnectionRemovingState('exit-2', true);\n plumber.setConnectionRemovingState('exit-2', false);\n\n const conn = (plumber as any).connections.get('exit-2');\n expect(conn.svgEl.classList.contains('removing')).to.be.false;\n\n exitEl.remove();\n targetEl.remove();\n });\n });\n\n describe('removeExitConnection', () => {\n it('removes a connection for an exit', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-3';\n const targetEl = document.createElement('div');\n targetEl.id = 'target-3';\n mockCanvas.appendChild(exitEl);\n mockCanvas.appendChild(targetEl);\n\n plumber.connectIds('node-1', 'exit-3', 'target-3');\n clock.tick(16);\n\n expect((plumber as any).connections.has('exit-3')).to.be.true;\n\n const result = plumber.removeExitConnection('exit-3');\n expect(result).to.be.true;\n expect((plumber as any).connections.has('exit-3')).to.be.false;\n\n exitEl.remove();\n targetEl.remove();\n });\n\n it('returns false when no connection exists', () => {\n const result = plumber.removeExitConnection('nonexistent');\n expect(result).to.be.false;\n });\n });\n\n describe('connectIds and processPendingConnections', () => {\n it('adds connection to pending connections', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n expect((plumber as any).pendingConnections.length).to.equal(1);\n });\n\n it('clears previous rAF when called multiple times', () => {\n const cancelSpy = stub(window, 'cancelAnimationFrame');\n const rafSpy = stub(window, 'requestAnimationFrame').returns(123 as any);\n\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n expect(cancelSpy).to.have.been.calledOnce;\n expect(rafSpy).to.have.been.calledTwice;\n\n cancelSpy.restore();\n rafSpy.restore();\n });\n });\n\n describe('removeNodeConnections', () => {\n it('removes inbound and outbound connections for a node', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-4';\n exitEl.classList.add('exit');\n const nodeEl = document.createElement('div');\n nodeEl.id = 'node-1';\n nodeEl.appendChild(exitEl);\n const targetEl = document.createElement('div');\n targetEl.id = 'target-4';\n mockCanvas.appendChild(nodeEl);\n mockCanvas.appendChild(targetEl);\n\n plumber.connectIds('node-1', 'exit-4', 'target-4');\n clock.tick(16);\n\n expect((plumber as any).connections.size).to.equal(1);\n\n plumber.removeNodeConnections('node-1', ['exit-4']);\n expect((plumber as any).connections.size).to.equal(0);\n\n nodeEl.remove();\n targetEl.remove();\n });\n });\n});\n"]}
@@ -1,53 +1,24 @@
1
1
  import { expect } from '@open-wc/testing';
2
- import { Plumber, SOURCE_DEFAULTS, TARGET_DEFAULTS } from '../src/flow/Plumber';
2
+ import { Plumber, calculateFlowchartPath } from '../src/flow/Plumber';
3
3
  import { stub, useFakeTimers } from 'sinon';
4
4
  describe('Plumber', () => {
5
5
  let plumber;
6
- let mockJsPlumb;
7
6
  let mockCanvas;
8
7
  let clock;
8
+ let mockElement;
9
9
  beforeEach(() => {
10
- // Use fake timers to control setTimeout
11
10
  clock = useFakeTimers();
12
- // Create mock canvas and make getElementById return a mock element
13
11
  mockCanvas = document.createElement('div');
14
- const mockElement = document.createElement('div');
15
- stub(document, 'getElementById').returns(mockElement);
16
- // Create a mock editor with fireCustomEvent
12
+ mockCanvas.id = 'canvas';
13
+ document.body.appendChild(mockCanvas);
14
+ mockElement = document.createElement('div');
15
+ mockElement.id = 'test-exit';
16
+ mockCanvas.appendChild(mockElement);
17
17
  const mockEditor = { fireCustomEvent: stub() };
18
- // Create a new plumber instance
19
18
  plumber = new Plumber(mockCanvas, mockEditor);
20
- // Replace the internal jsPlumb instance with mocks
21
- mockJsPlumb = {
22
- getConnections: stub().returns([]),
23
- addClass: stub(),
24
- removeClass: stub(),
25
- batch: stub().callsFake((fn) => fn()),
26
- addEndpoint: stub().returns({}),
27
- connect: stub(),
28
- getEndpoints: stub().returns([
29
- { elementId: 'test-from', addClass: stub() }
30
- ]),
31
- select: stub().returns({
32
- deleteAll: stub()
33
- }),
34
- selectEndpoints: stub().returns({
35
- deleteAll: stub()
36
- }),
37
- deleteConnection: stub(),
38
- removeAllEndpoints: stub(),
39
- repaintEverything: stub(),
40
- revalidate: stub(),
41
- bind: stub()
42
- };
43
- plumber.jsPlumb = mockJsPlumb;
44
- // Reset the connectionWait to avoid timing issues
45
- plumber.connectionWait = null;
46
19
  });
47
20
  afterEach(() => {
48
- var _a, _b;
49
- // Restore the original document.getElementById
50
- (_b = (_a = document.getElementById).restore) === null || _b === void 0 ? void 0 : _b.call(_a);
21
+ mockCanvas.remove();
51
22
  clock.restore();
52
23
  });
53
24
  describe('constructor', () => {
@@ -55,79 +26,117 @@ describe('Plumber', () => {
55
26
  expect(plumber).to.be.instanceOf(Plumber);
56
27
  });
57
28
  });
58
- describe('makeTarget', () => {
59
- it('creates a target endpoint for the specified element', () => {
60
- plumber.makeTarget('test-target');
61
- expect(mockJsPlumb.addEndpoint).to.have.been.called;
29
+ describe('makeSource', () => {
30
+ it('registers a mousedown listener on the exit element', () => {
31
+ const exitEl = document.createElement('div');
32
+ exitEl.id = 'exit-1';
33
+ mockCanvas.appendChild(exitEl);
34
+ plumber.makeSource('exit-1');
35
+ // Source should be tracked
36
+ expect(plumber.sources.has('exit-1')).to.be.true;
37
+ exitEl.remove();
38
+ });
39
+ it('cleans up previous listener when called again', () => {
40
+ const exitEl = document.createElement('div');
41
+ exitEl.id = 'exit-2';
42
+ mockCanvas.appendChild(exitEl);
43
+ plumber.makeSource('exit-2');
44
+ plumber.makeSource('exit-2');
45
+ expect(plumber.sources.has('exit-2')).to.be.true;
46
+ exitEl.remove();
62
47
  });
63
48
  });
64
- describe('makeSource', () => {
65
- it('creates a source endpoint for the specified element', () => {
66
- plumber.makeSource('test-source');
67
- expect(mockJsPlumb.addEndpoint).to.have.been.called;
49
+ describe('makeTarget', () => {
50
+ it('is a no-op', () => {
51
+ // Should not throw
52
+ plumber.makeTarget('test-node');
68
53
  });
69
54
  });
70
55
  describe('connectIds', () => {
71
56
  it('adds connection to pending connections and processes them', () => {
72
57
  plumber.connectIds('test-node', 'test-from', 'test-to');
73
- // Verify pendingConnections has the new connection
74
58
  expect(plumber.pendingConnections.length).to.equal(1);
75
- // Advance timer to trigger the timeout
76
- clock.tick(51); // Just past the 50ms timeout
77
- // Now the batch should have been called
78
- expect(mockJsPlumb.batch).to.have.been.called;
79
59
  });
80
60
  });
81
61
  describe('processPendingConnections', () => {
82
- it('processes pending connections with timeout', () => {
83
- // Add a connection to pending connections
84
- plumber.connectIds('test-node', 'test-from', 'test-to');
85
- // Fast-forward clock past the timeout
86
- clock.tick(51); // Just past the 50ms timeout
87
- expect(mockJsPlumb.batch).to.have.been.called;
88
- });
89
- it('creates endpoints and connections for pending connections', () => {
90
- plumber.connectIds('test-node', 'test-from', 'test-to');
91
- // Fast-forward clock past the timeout
92
- clock.tick(51); // Just past the 50ms timeout
93
- expect(mockJsPlumb.addEndpoint).to.have.been.called;
94
- expect(mockJsPlumb.connect).to.have.been.called;
95
- });
96
- it('clears existing timeout when called multiple times', () => {
97
- // Set up spies for window.setTimeout and window.clearTimeout
98
- const clearTimeoutSpy = stub(window, 'clearTimeout');
99
- const setTimeoutSpy = stub(window, 'setTimeout').returns(123);
100
- // Call twice
62
+ it('clears existing rAF when called multiple times', () => {
63
+ const cancelSpy = stub(window, 'cancelAnimationFrame');
64
+ const rafSpy = stub(window, 'requestAnimationFrame').returns(123);
101
65
  plumber.processPendingConnections();
102
66
  plumber.processPendingConnections();
103
- // Should have called clearTimeout once and setTimeout twice
104
- expect(clearTimeoutSpy).to.have.been.calledOnce;
105
- expect(setTimeoutSpy).to.have.been.calledTwice;
106
- // Clean up
107
- clearTimeoutSpy.restore();
108
- setTimeoutSpy.restore();
109
- });
110
- it('handles empty pending connections', () => {
111
- // Call without adding any connections
112
- plumber.processPendingConnections();
113
- // Fast-forward clock past the timeout
114
- clock.tick(51); // Just past the 50ms timeout
115
- expect(mockJsPlumb.batch).to.have.been.called;
67
+ expect(cancelSpy).to.have.been.calledOnce;
68
+ expect(rafSpy).to.have.been.calledTwice;
69
+ cancelSpy.restore();
70
+ rafSpy.restore();
116
71
  });
117
72
  });
118
- describe('constants', () => {
119
- it('has correct properties in SOURCE_DEFAULTS', () => {
120
- expect(SOURCE_DEFAULTS).to.have.property('endpoint');
121
- expect(SOURCE_DEFAULTS).to.have.property('anchors');
122
- expect(SOURCE_DEFAULTS).to.have.property('maxConnections');
123
- expect(SOURCE_DEFAULTS).to.have.property('source');
73
+ describe('event system', () => {
74
+ it('supports on/off/notify pattern', () => {
75
+ let received = null;
76
+ const handler = (info) => {
77
+ received = info;
78
+ };
79
+ plumber.on('test-event', handler);
80
+ plumber.notifyListeners('test-event', { data: 'test' });
81
+ expect(received).to.deep.equal({ data: 'test' });
82
+ received = null;
83
+ plumber.off('test-event', handler);
84
+ plumber.notifyListeners('test-event', { data: 'test2' });
85
+ expect(received).to.be.null;
124
86
  });
125
- it('has correct properties in TARGET_DEFAULTS', () => {
126
- expect(TARGET_DEFAULTS).to.have.property('endpoint');
127
- expect(TARGET_DEFAULTS).to.have.property('anchor');
128
- expect(TARGET_DEFAULTS).to.have.property('maxConnections');
129
- expect(TARGET_DEFAULTS).to.have.property('target');
87
+ });
88
+ describe('reset', () => {
89
+ it('clears all state', () => {
90
+ plumber.connectIds('test-node', 'test-from', 'test-to');
91
+ plumber.reset();
92
+ expect(plumber.pendingConnections.length).to.equal(0);
93
+ expect(plumber.connections.size).to.equal(0);
94
+ expect(plumber.sources.size).to.equal(0);
130
95
  });
131
96
  });
132
97
  });
98
+ describe('calculateFlowchartPath', () => {
99
+ it('generates a straight vertical path when source and target are aligned', () => {
100
+ const path = calculateFlowchartPath(100, 0, 100, 100);
101
+ expect(path).to.include('M 100 0');
102
+ expect(path).to.include('L 100 100');
103
+ // Should not contain Q (quadratic curve) for aligned points
104
+ expect(path).to.not.include('Q');
105
+ });
106
+ it('generates a path with corners when source and target are offset', () => {
107
+ const path = calculateFlowchartPath(50, 0, 150, 200);
108
+ expect(path).to.include('M 50 0');
109
+ expect(path).to.include('Q'); // Should have rounded corners
110
+ expect(path).to.include('L 150 200');
111
+ });
112
+ it('handles custom stub and corner radius', () => {
113
+ const path = calculateFlowchartPath(0, 0, 100, 100, 30, 15, 10);
114
+ expect(path).to.include('M 0 0');
115
+ expect(path).to.include('L 100 100');
116
+ });
117
+ it('handles cases where vertical space is tight by using reduced-radius corners', () => {
118
+ // With stubs of 20+10=30, and only 35 total vertical space, there's only 5px for corners
119
+ const path = calculateFlowchartPath(50, 0, 150, 35);
120
+ expect(path).to.include('M 50 0');
121
+ // Should still use rounded corners (L-shape with curves)
122
+ expect(path).to.include('Q');
123
+ });
124
+ it('enforces midY is always below source exit for top face', () => {
125
+ // Target above source — midY should not go above sourceY + stubStart
126
+ const path = calculateFlowchartPath(50, 100, 150, 50);
127
+ expect(path).to.include('M 50 100');
128
+ // Should still exit downward with a curve at exitY (120)
129
+ expect(path).to.include('Q');
130
+ });
131
+ it('generates a path entering from the left face', () => {
132
+ const path = calculateFlowchartPath(50, 0, 150, 100, 20, 10, 5, 'left');
133
+ expect(path).to.include('M 50 0');
134
+ expect(path).to.include('L 150 100'); // ends at target
135
+ });
136
+ it('generates a path entering from the right face', () => {
137
+ const path = calculateFlowchartPath(150, 0, 50, 100, 20, 10, 5, 'right');
138
+ expect(path).to.include('M 150 0');
139
+ expect(path).to.include('L 50 100'); // ends at target
140
+ });
141
+ });
133
142
  //# sourceMappingURL=temba-flow-plumber.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"temba-flow-plumber.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { Plumber, SOURCE_DEFAULTS, TARGET_DEFAULTS } from '../src/flow/Plumber';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\n\ndescribe('Plumber', () => {\n let plumber: Plumber;\n let mockJsPlumb: any;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n\n beforeEach(() => {\n // Use fake timers to control setTimeout\n clock = useFakeTimers();\n\n // Create mock canvas and make getElementById return a mock element\n mockCanvas = document.createElement('div');\n const mockElement = document.createElement('div');\n stub(document, 'getElementById').returns(mockElement);\n\n // Create a mock editor with fireCustomEvent\n const mockEditor = { fireCustomEvent: stub() };\n\n // Create a new plumber instance\n plumber = new Plumber(mockCanvas, mockEditor);\n\n // Replace the internal jsPlumb instance with mocks\n mockJsPlumb = {\n getConnections: stub().returns([]),\n addClass: stub(),\n removeClass: stub(),\n batch: stub().callsFake((fn) => fn()),\n addEndpoint: stub().returns({}),\n connect: stub(),\n getEndpoints: stub().returns([\n { elementId: 'test-from', addClass: stub() }\n ]),\n select: stub().returns({\n deleteAll: stub()\n }),\n selectEndpoints: stub().returns({\n deleteAll: stub()\n }),\n deleteConnection: stub(),\n removeAllEndpoints: stub(),\n repaintEverything: stub(),\n revalidate: stub(),\n bind: stub()\n };\n\n (plumber as any).jsPlumb = mockJsPlumb;\n // Reset the connectionWait to avoid timing issues\n (plumber as any).connectionWait = null;\n });\n\n afterEach(() => {\n // Restore the original document.getElementById\n (document.getElementById as any).restore?.();\n clock.restore();\n });\n\n describe('constructor', () => {\n it('creates a new plumber instance', () => {\n expect(plumber).to.be.instanceOf(Plumber);\n });\n });\n\n describe('makeTarget', () => {\n it('creates a target endpoint for the specified element', () => {\n plumber.makeTarget('test-target');\n expect(mockJsPlumb.addEndpoint).to.have.been.called;\n });\n });\n\n describe('makeSource', () => {\n it('creates a source endpoint for the specified element', () => {\n plumber.makeSource('test-source');\n expect(mockJsPlumb.addEndpoint).to.have.been.called;\n });\n });\n\n describe('connectIds', () => {\n it('adds connection to pending connections and processes them', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n\n // Verify pendingConnections has the new connection\n expect((plumber as any).pendingConnections.length).to.equal(1);\n\n // Advance timer to trigger the timeout\n clock.tick(51); // Just past the 50ms timeout\n\n // Now the batch should have been called\n expect(mockJsPlumb.batch).to.have.been.called;\n });\n });\n\n describe('processPendingConnections', () => {\n it('processes pending connections with timeout', () => {\n // Add a connection to pending connections\n plumber.connectIds('test-node', 'test-from', 'test-to');\n\n // Fast-forward clock past the timeout\n clock.tick(51); // Just past the 50ms timeout\n\n expect(mockJsPlumb.batch).to.have.been.called;\n });\n\n it('creates endpoints and connections for pending connections', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n\n // Fast-forward clock past the timeout\n clock.tick(51); // Just past the 50ms timeout\n\n expect(mockJsPlumb.addEndpoint).to.have.been.called;\n expect(mockJsPlumb.connect).to.have.been.called;\n });\n\n it('clears existing timeout when called multiple times', () => {\n // Set up spies for window.setTimeout and window.clearTimeout\n const clearTimeoutSpy = stub(window, 'clearTimeout');\n const setTimeoutSpy = stub(window, 'setTimeout').returns(123 as any);\n\n // Call twice\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n // Should have called clearTimeout once and setTimeout twice\n expect(clearTimeoutSpy).to.have.been.calledOnce;\n expect(setTimeoutSpy).to.have.been.calledTwice;\n\n // Clean up\n clearTimeoutSpy.restore();\n setTimeoutSpy.restore();\n });\n\n it('handles empty pending connections', () => {\n // Call without adding any connections\n plumber.processPendingConnections();\n\n // Fast-forward clock past the timeout\n clock.tick(51); // Just past the 50ms timeout\n\n expect(mockJsPlumb.batch).to.have.been.called;\n });\n });\n\n describe('constants', () => {\n it('has correct properties in SOURCE_DEFAULTS', () => {\n expect(SOURCE_DEFAULTS).to.have.property('endpoint');\n expect(SOURCE_DEFAULTS).to.have.property('anchors');\n expect(SOURCE_DEFAULTS).to.have.property('maxConnections');\n expect(SOURCE_DEFAULTS).to.have.property('source');\n });\n\n it('has correct properties in TARGET_DEFAULTS', () => {\n expect(TARGET_DEFAULTS).to.have.property('endpoint');\n expect(TARGET_DEFAULTS).to.have.property('anchor');\n expect(TARGET_DEFAULTS).to.have.property('maxConnections');\n expect(TARGET_DEFAULTS).to.have.property('target');\n });\n });\n});\n"]}
1
+ {"version":3,"file":"temba-flow-plumber.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { Plumber, calculateFlowchartPath } from '../src/flow/Plumber';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\n\ndescribe('Plumber', () => {\n let plumber: Plumber;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n let mockElement: HTMLElement;\n\n beforeEach(() => {\n clock = useFakeTimers();\n\n mockCanvas = document.createElement('div');\n mockCanvas.id = 'canvas';\n document.body.appendChild(mockCanvas);\n\n mockElement = document.createElement('div');\n mockElement.id = 'test-exit';\n mockCanvas.appendChild(mockElement);\n\n const mockEditor = { fireCustomEvent: stub() };\n plumber = new Plumber(mockCanvas, mockEditor);\n });\n\n afterEach(() => {\n mockCanvas.remove();\n clock.restore();\n });\n\n describe('constructor', () => {\n it('creates a new plumber instance', () => {\n expect(plumber).to.be.instanceOf(Plumber);\n });\n });\n\n describe('makeSource', () => {\n it('registers a mousedown listener on the exit element', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-1';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-1');\n\n // Source should be tracked\n expect((plumber as any).sources.has('exit-1')).to.be.true;\n\n exitEl.remove();\n });\n\n it('cleans up previous listener when called again', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-2';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-2');\n plumber.makeSource('exit-2');\n\n expect((plumber as any).sources.has('exit-2')).to.be.true;\n\n exitEl.remove();\n });\n });\n\n describe('makeTarget', () => {\n it('is a no-op', () => {\n // Should not throw\n plumber.makeTarget('test-node');\n });\n });\n\n describe('connectIds', () => {\n it('adds connection to pending connections and processes them', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n expect((plumber as any).pendingConnections.length).to.equal(1);\n });\n });\n\n describe('processPendingConnections', () => {\n it('clears existing rAF when called multiple times', () => {\n const cancelSpy = stub(window, 'cancelAnimationFrame');\n const rafSpy = stub(window, 'requestAnimationFrame').returns(123 as any);\n\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n expect(cancelSpy).to.have.been.calledOnce;\n expect(rafSpy).to.have.been.calledTwice;\n\n cancelSpy.restore();\n rafSpy.restore();\n });\n });\n\n describe('event system', () => {\n it('supports on/off/notify pattern', () => {\n let received = null;\n const handler = (info: any) => {\n received = info;\n };\n\n plumber.on('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test' });\n expect(received).to.deep.equal({ data: 'test' });\n\n received = null;\n plumber.off('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test2' });\n expect(received).to.be.null;\n });\n });\n\n describe('reset', () => {\n it('clears all state', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n plumber.reset();\n\n expect((plumber as any).pendingConnections.length).to.equal(0);\n expect((plumber as any).connections.size).to.equal(0);\n expect((plumber as any).sources.size).to.equal(0);\n });\n });\n});\n\ndescribe('calculateFlowchartPath', () => {\n it('generates a straight vertical path when source and target are aligned', () => {\n const path = calculateFlowchartPath(100, 0, 100, 100);\n expect(path).to.include('M 100 0');\n expect(path).to.include('L 100 100');\n // Should not contain Q (quadratic curve) for aligned points\n expect(path).to.not.include('Q');\n });\n\n it('generates a path with corners when source and target are offset', () => {\n const path = calculateFlowchartPath(50, 0, 150, 200);\n expect(path).to.include('M 50 0');\n expect(path).to.include('Q'); // Should have rounded corners\n expect(path).to.include('L 150 200');\n });\n\n it('handles custom stub and corner radius', () => {\n const path = calculateFlowchartPath(0, 0, 100, 100, 30, 15, 10);\n expect(path).to.include('M 0 0');\n expect(path).to.include('L 100 100');\n });\n\n it('handles cases where vertical space is tight by using reduced-radius corners', () => {\n // With stubs of 20+10=30, and only 35 total vertical space, there's only 5px for corners\n const path = calculateFlowchartPath(50, 0, 150, 35);\n expect(path).to.include('M 50 0');\n // Should still use rounded corners (L-shape with curves)\n expect(path).to.include('Q');\n });\n\n it('enforces midY is always below source exit for top face', () => {\n // Target above source — midY should not go above sourceY + stubStart\n const path = calculateFlowchartPath(50, 100, 150, 50);\n expect(path).to.include('M 50 100');\n // Should still exit downward with a curve at exitY (120)\n expect(path).to.include('Q');\n });\n\n it('generates a path entering from the left face', () => {\n const path = calculateFlowchartPath(50, 0, 150, 100, 20, 10, 5, 'left');\n expect(path).to.include('M 50 0');\n expect(path).to.include('L 150 100'); // ends at target\n });\n\n it('generates a path entering from the right face', () => {\n const path = calculateFlowchartPath(150, 0, 50, 100, 20, 10, 5, 'right');\n expect(path).to.include('M 150 0');\n expect(path).to.include('L 50 100'); // ends at target\n });\n});\n"]}
@@ -121,9 +121,9 @@ describe('temba-node-type-selector', () => {
121
121
  // get all node item titles
122
122
  const nodeItems = (_a = selector.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.node-item-title');
123
123
  const titles = Array.from(nodeItems || []).map((item) => { var _a; return (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim(); });
124
- // voice flow should have Say Message and Play Audio
124
+ // voice flow should have Say Message and Play Recording
125
125
  expect(titles).to.include('Say Message');
126
- expect(titles).to.include('Play Audio');
126
+ expect(titles).to.include('Play Recording');
127
127
  });
128
128
  it('filters actions by flow type - message flow should not show voice-only actions', async () => {
129
129
  var _a;
@@ -135,9 +135,9 @@ describe('temba-node-type-selector', () => {
135
135
  // get all node item titles
136
136
  const nodeItems = (_a = selector.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.node-item-title');
137
137
  const titles = Array.from(nodeItems || []).map((item) => { var _a; return (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim(); });
138
- // message flow should not have Say Message or Play Audio
138
+ // message flow should not have Say Message or Play Recording
139
139
  expect(titles).to.not.include('Say Message');
140
- expect(titles).to.not.include('Play Audio');
140
+ expect(titles).to.not.include('Play Recording');
141
141
  });
142
142
  it('filters splits by flow type - message flow should show wait for response', async () => {
143
143
  var _a;
@@ -164,9 +164,9 @@ describe('temba-node-type-selector', () => {
164
164
  const titles = Array.from(nodeItems || []).map((item) => { var _a; return (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim(); });
165
165
  // voice flow should not have Wait for Response
166
166
  expect(titles).to.not.include('Wait for Response');
167
- // but should have Wait for Digits and Wait for Menu Selection
167
+ // but should have Wait for Digits and Wait for Menu
168
168
  expect(titles).to.include('Wait for Digits');
169
- expect(titles).to.include('Wait for Menu Selection');
169
+ expect(titles).to.include('Wait for Menu');
170
170
  });
171
171
  it('filters by features - AI feature enables AI splits', async () => {
172
172
  var _a;