@nyaruka/temba-components 0.138.4 → 0.139.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 (69) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/locales/es.js +5 -5
  3. package/dist/locales/es.js.map +1 -1
  4. package/dist/locales/fr.js +5 -5
  5. package/dist/locales/fr.js.map +1 -1
  6. package/dist/locales/locale-codes.js +2 -11
  7. package/dist/locales/locale-codes.js.map +1 -1
  8. package/dist/locales/pt.js +5 -5
  9. package/dist/locales/pt.js.map +1 -1
  10. package/dist/temba-components.js +816 -852
  11. package/dist/temba-components.js.map +1 -1
  12. package/out-tsc/src/display/FloatingTab.js +23 -30
  13. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  14. package/out-tsc/src/flow/CanvasMenu.js +5 -3
  15. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  16. package/out-tsc/src/flow/CanvasNode.js +6 -7
  17. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  18. package/out-tsc/src/flow/Editor.js +152 -235
  19. package/out-tsc/src/flow/Editor.js.map +1 -1
  20. package/out-tsc/src/flow/Plumber.js +757 -403
  21. package/out-tsc/src/flow/Plumber.js.map +1 -1
  22. package/out-tsc/src/flow/utils.js +138 -66
  23. package/out-tsc/src/flow/utils.js.map +1 -1
  24. package/out-tsc/src/interfaces.js +1 -0
  25. package/out-tsc/src/interfaces.js.map +1 -1
  26. package/out-tsc/src/list/TicketList.js +4 -1
  27. package/out-tsc/src/list/TicketList.js.map +1 -1
  28. package/out-tsc/src/live/ContactChat.js +18 -1
  29. package/out-tsc/src/live/ContactChat.js.map +1 -1
  30. package/out-tsc/src/locales/es.js +5 -5
  31. package/out-tsc/src/locales/es.js.map +1 -1
  32. package/out-tsc/src/locales/fr.js +5 -5
  33. package/out-tsc/src/locales/fr.js.map +1 -1
  34. package/out-tsc/src/locales/locale-codes.js +2 -11
  35. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  36. package/out-tsc/src/locales/pt.js +5 -5
  37. package/out-tsc/src/locales/pt.js.map +1 -1
  38. package/out-tsc/src/simulator/Simulator.js +1 -0
  39. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  40. package/out-tsc/test/temba-floating-tab.test.js +4 -6
  41. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  42. package/out-tsc/test/temba-flow-collision.test.js +221 -223
  43. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  44. package/out-tsc/test/temba-flow-editor.test.js +0 -2
  45. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  46. package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
  47. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  48. package/out-tsc/test/temba-flow-plumber.test.js +102 -93
  49. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/display/FloatingTab.ts +22 -31
  52. package/src/flow/CanvasMenu.ts +8 -3
  53. package/src/flow/CanvasNode.ts +6 -7
  54. package/src/flow/Editor.ts +184 -279
  55. package/src/flow/Plumber.ts +1011 -457
  56. package/src/flow/utils.ts +162 -84
  57. package/src/interfaces.ts +2 -1
  58. package/src/list/TicketList.ts +4 -1
  59. package/src/live/ContactChat.ts +19 -1
  60. package/src/locales/es.ts +13 -18
  61. package/src/locales/fr.ts +13 -18
  62. package/src/locales/locale-codes.ts +2 -11
  63. package/src/locales/pt.ts +13 -18
  64. package/src/simulator/Simulator.ts +1 -0
  65. package/test/temba-floating-tab.test.ts +4 -6
  66. package/test/temba-flow-collision.test.ts +225 -303
  67. package/test/temba-flow-editor.test.ts +0 -2
  68. package/test/temba-flow-plumber-connections.test.ts +97 -97
  69. package/test/temba-flow-plumber.test.ts +116 -103
@@ -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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.138.4",
3
+ "version": "0.139.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",