@nyaruka/temba-components 0.141.1 → 0.142.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 (193) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/static/svg/index.svg +1 -1
  3. package/dist/temba-components.js +849 -655
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/Icons.js +3 -1
  6. package/out-tsc/src/Icons.js.map +1 -1
  7. package/out-tsc/src/display/Button.js +2 -2
  8. package/out-tsc/src/display/Button.js.map +1 -1
  9. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasMenu.js +24 -1
  11. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  12. package/out-tsc/src/flow/CanvasNode.js +7 -2
  13. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  14. package/out-tsc/src/flow/Editor.js +654 -66
  15. package/out-tsc/src/flow/Editor.js.map +1 -1
  16. package/out-tsc/src/flow/NodeEditor.js +8 -5
  17. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  18. package/out-tsc/src/flow/Plumber.js +40 -28
  19. package/out-tsc/src/flow/Plumber.js.map +1 -1
  20. package/out-tsc/src/flow/actions/send_msg.js +2 -1
  21. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  22. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  23. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  24. package/out-tsc/src/flow/reflow.js +393 -0
  25. package/out-tsc/src/flow/reflow.js.map +1 -0
  26. package/out-tsc/src/flow/types.js.map +1 -1
  27. package/out-tsc/src/flow/utils.js +18 -3
  28. package/out-tsc/src/flow/utils.js.map +1 -1
  29. package/out-tsc/src/form/Compose.js +5 -0
  30. package/out-tsc/src/form/Compose.js.map +1 -1
  31. package/out-tsc/src/form/FieldRenderer.js +1 -3
  32. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  33. package/out-tsc/src/layout/Dialog.js +2 -0
  34. package/out-tsc/src/layout/Dialog.js.map +1 -1
  35. package/out-tsc/src/list/SortableList.js +39 -19
  36. package/out-tsc/src/list/SortableList.js.map +1 -1
  37. package/out-tsc/test/temba-canvas-menu.test.js +44 -0
  38. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
  39. package/out-tsc/test/temba-flow-collision.test.js +25 -0
  40. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  41. package/out-tsc/test/temba-flow-editor-zoom.test.js +491 -0
  42. package/out-tsc/test/temba-flow-editor-zoom.test.js.map +1 -0
  43. package/out-tsc/test/temba-flow-editor.test.js +145 -1
  44. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  45. package/out-tsc/test/temba-flow-node-drag.test.js +123 -0
  46. package/out-tsc/test/temba-flow-node-drag.test.js.map +1 -1
  47. package/out-tsc/test/temba-flow-plumber.test.js +31 -0
  48. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  49. package/out-tsc/test/temba-flow-reflow.test.js +472 -0
  50. package/out-tsc/test/temba-flow-reflow.test.js.map +1 -0
  51. package/out-tsc/test/temba-sortable-list.test.js +93 -0
  52. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  53. package/package.json +1 -1
  54. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  55. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  56. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  57. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  58. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  59. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  60. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  61. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  62. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  63. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  64. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  65. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  66. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  67. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  68. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  69. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  70. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  71. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  72. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  73. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  74. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  75. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  76. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  77. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  79. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  80. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  81. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  82. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  83. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  84. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  85. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  86. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  87. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  88. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  89. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  90. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  91. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  92. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  93. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  94. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  95. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  96. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  97. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  98. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  99. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  100. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  101. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  102. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  103. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  104. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  105. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  106. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  107. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  108. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  109. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  110. package/screenshots/truth/list/fields-dragging.png +0 -0
  111. package/screenshots/truth/list/sortable-dragging.png +0 -0
  112. package/screenshots/truth/modax/simple.png +0 -0
  113. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  114. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  115. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  116. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  117. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  118. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  119. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  120. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  121. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  122. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  123. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  124. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  125. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  134. package/src/Icons.ts +3 -1
  135. package/src/display/Button.ts +2 -2
  136. package/src/display/FloatingTab.ts +1 -1
  137. package/src/flow/CanvasMenu.ts +28 -3
  138. package/src/flow/CanvasNode.ts +7 -2
  139. package/src/flow/Editor.ts +755 -75
  140. package/src/flow/NodeEditor.ts +8 -4
  141. package/src/flow/Plumber.ts +65 -35
  142. package/src/flow/actions/send_msg.ts +2 -1
  143. package/src/flow/nodes/wait_for_response.ts +1 -1
  144. package/src/flow/reflow.ts +534 -0
  145. package/src/flow/types.ts +1 -0
  146. package/src/flow/utils.ts +19 -3
  147. package/src/form/Compose.ts +5 -0
  148. package/src/form/FieldRenderer.ts +1 -3
  149. package/src/layout/Dialog.ts +2 -0
  150. package/src/list/SortableList.ts +40 -19
  151. package/static/svg/index.svg +1 -1
  152. package/static/svg/work/traced/expand-06.svg +1 -0
  153. package/static/svg/work/used/expand-06.svg +3 -0
  154. package/test/temba-canvas-menu.test.ts +55 -0
  155. package/test/temba-flow-collision.test.ts +31 -0
  156. package/test/temba-flow-editor-zoom.test.ts +583 -0
  157. package/test/temba-flow-editor.test.ts +187 -1
  158. package/test/temba-flow-node-drag.test.ts +171 -0
  159. package/test/temba-flow-plumber.test.ts +38 -0
  160. package/test/temba-flow-reflow.test.ts +703 -0
  161. package/test/temba-sortable-list.test.ts +120 -0
  162. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  163. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  164. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  165. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  166. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  167. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  168. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  169. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  170. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  171. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  172. package/screenshots/truth/compose/attachments-with-failures.png +0 -0
  173. package/screenshots/truth/compose/attachments-with-files-and-failures.png +0 -0
  174. package/screenshots/truth/contacts/tickets-assignment.png +0 -0
  175. package/screenshots/truth/contacts/tickets.png +0 -0
  176. package/screenshots/truth/flow/editor-basic.png +0 -0
  177. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  178. package/screenshots/truth/formfield/no-errors.png +0 -0
  179. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  180. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  181. package/screenshots/truth/omnibox/selected.png +0 -0
  182. package/screenshots/truth/select/enabled-multi-selection.png +0 -0
  183. package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
  184. package/screenshots/truth/select/endpoint-initial-value.png +0 -0
  185. package/screenshots/truth/select/initial-value.png +0 -0
  186. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  187. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  188. package/screenshots/truth/select/selected-multi-test.png +0 -0
  189. package/screenshots/truth/select/value-initial.png +0 -0
  190. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  191. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  192. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  193. package/screenshots/truth/webchat/connecting-state.png +0 -0
@@ -0,0 +1,472 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { calculateLayeredLayout, placeStickyNotes } from '../src/flow/reflow';
3
+ // Helper to create a minimal Node with exits
4
+ function makeNode(uuid, exits) {
5
+ return {
6
+ uuid,
7
+ actions: [],
8
+ exits: exits.map((e, i) => ({
9
+ uuid: `${uuid}-exit-${i}`,
10
+ destination_uuid: e.destination_uuid
11
+ }))
12
+ };
13
+ }
14
+ // Helper to return a fixed size for all nodes
15
+ function constantSize(width, height) {
16
+ return () => ({ width, height });
17
+ }
18
+ describe('Reflow Layout', () => {
19
+ describe('calculateLayeredLayout', () => {
20
+ it('returns empty object for empty nodes', () => {
21
+ const result = calculateLayeredLayout([], {}, 'start', constantSize(200, 100));
22
+ expect(Object.keys(result)).to.have.length(0);
23
+ });
24
+ it('places a single node at the origin', () => {
25
+ const nodes = [makeNode('A', [])];
26
+ const nodeUIs = { A: { position: { left: 500, top: 500 } } };
27
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
28
+ expect(result['A']).to.not.be.undefined;
29
+ expect(result['A'].left).to.equal(0);
30
+ expect(result['A'].top).to.equal(0);
31
+ });
32
+ it('places a linear chain vertically', () => {
33
+ // A -> B -> C
34
+ const nodes = [
35
+ makeNode('A', [{ destination_uuid: 'B' }]),
36
+ makeNode('B', [{ destination_uuid: 'C' }]),
37
+ makeNode('C', [])
38
+ ];
39
+ const nodeUIs = {
40
+ A: { position: { left: 0, top: 0 } },
41
+ B: { position: { left: 0, top: 200 } },
42
+ C: { position: { left: 0, top: 400 } }
43
+ };
44
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
45
+ // All should be at left=0 (centered under parent which is at 0)
46
+ // A at layer 0, B at layer 1, C at layer 2
47
+ expect(result['A'].top).to.equal(0);
48
+ expect(result['B'].top).to.be.greaterThan(result['A'].top);
49
+ expect(result['C'].top).to.be.greaterThan(result['B'].top);
50
+ });
51
+ it('places siblings at the same vertical level', () => {
52
+ // A -> B, A -> C
53
+ const nodes = [
54
+ makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),
55
+ makeNode('B', []),
56
+ makeNode('C', [])
57
+ ];
58
+ const nodeUIs = {
59
+ A: { position: { left: 0, top: 0 } },
60
+ B: { position: { left: 0, top: 200 } },
61
+ C: { position: { left: 300, top: 200 } }
62
+ };
63
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
64
+ // B and C should be on the same layer (same top)
65
+ expect(result['B'].top).to.equal(result['C'].top);
66
+ expect(result['B'].top).to.be.greaterThan(result['A'].top);
67
+ // B and C should be side by side
68
+ expect(result['B'].left).to.not.equal(result['C'].left);
69
+ });
70
+ it('snaps all positions to grid (multiples of 20)', () => {
71
+ const nodes = [
72
+ makeNode('A', [{ destination_uuid: 'B' }]),
73
+ makeNode('B', [])
74
+ ];
75
+ const nodeUIs = {
76
+ A: { position: { left: 0, top: 0 } },
77
+ B: { position: { left: 0, top: 200 } }
78
+ };
79
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
80
+ for (const uuid of Object.keys(result)) {
81
+ expect(result[uuid].left % 20).to.equal(0);
82
+ expect(result[uuid].top % 20).to.equal(0);
83
+ }
84
+ });
85
+ it('handles cycles (back-edges) without infinite loops', () => {
86
+ // A -> B -> A (cycle)
87
+ const nodes = [
88
+ makeNode('A', [{ destination_uuid: 'B' }]),
89
+ makeNode('B', [{ destination_uuid: 'A' }])
90
+ ];
91
+ const nodeUIs = {
92
+ A: { position: { left: 0, top: 0 } },
93
+ B: { position: { left: 0, top: 200 } }
94
+ };
95
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
96
+ expect(result['A']).to.not.be.undefined;
97
+ expect(result['B']).to.not.be.undefined;
98
+ // A should be above B (layer 0 vs layer 1)
99
+ expect(result['A'].top).to.be.lessThan(result['B'].top);
100
+ });
101
+ it('handles diamond-shaped flows (merge nodes)', () => {
102
+ // A -> B, A -> C, B -> D, C -> D
103
+ const nodes = [
104
+ makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),
105
+ makeNode('B', [{ destination_uuid: 'D' }]),
106
+ makeNode('C', [{ destination_uuid: 'D' }]),
107
+ makeNode('D', [])
108
+ ];
109
+ const nodeUIs = {
110
+ A: { position: { left: 0, top: 0 } },
111
+ B: { position: { left: 0, top: 200 } },
112
+ C: { position: { left: 300, top: 200 } },
113
+ D: { position: { left: 150, top: 400 } }
114
+ };
115
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
116
+ // D should be below both B and C (longest path assignment)
117
+ expect(result['D'].top).to.be.greaterThan(result['B'].top);
118
+ expect(result['D'].top).to.be.greaterThan(result['C'].top);
119
+ // B and C on same layer
120
+ expect(result['B'].top).to.equal(result['C'].top);
121
+ });
122
+ it('handles deduplicated exits (same destination from multiple exits)', () => {
123
+ // A has two exits both pointing to B
124
+ const nodes = [
125
+ makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'B' }]),
126
+ makeNode('B', [])
127
+ ];
128
+ const nodeUIs = {
129
+ A: { position: { left: 0, top: 0 } },
130
+ B: { position: { left: 0, top: 200 } }
131
+ };
132
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
133
+ expect(result['A']).to.not.be.undefined;
134
+ expect(result['B']).to.not.be.undefined;
135
+ expect(result['B'].top).to.be.greaterThan(result['A'].top);
136
+ });
137
+ it('handles exits with no destination', () => {
138
+ const nodes = [
139
+ makeNode('A', [
140
+ { destination_uuid: undefined },
141
+ { destination_uuid: 'B' }
142
+ ]),
143
+ makeNode('B', [{ destination_uuid: undefined }])
144
+ ];
145
+ const nodeUIs = {
146
+ A: { position: { left: 0, top: 0 } },
147
+ B: { position: { left: 0, top: 200 } }
148
+ };
149
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
150
+ expect(result['A']).to.not.be.undefined;
151
+ expect(result['B']).to.not.be.undefined;
152
+ });
153
+ it('handles disconnected nodes', () => {
154
+ // A -> B, C is disconnected (no edges at all)
155
+ const nodes = [
156
+ makeNode('A', [{ destination_uuid: 'B' }]),
157
+ makeNode('B', []),
158
+ makeNode('C', [])
159
+ ];
160
+ const nodeUIs = {
161
+ A: { position: { left: 0, top: 0 } },
162
+ B: { position: { left: 0, top: 200 } },
163
+ C: { position: { left: 300, top: 300 } }
164
+ };
165
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
166
+ // All nodes should receive positions
167
+ expect(result['A']).to.not.be.undefined;
168
+ expect(result['B']).to.not.be.undefined;
169
+ expect(result['C']).to.not.be.undefined;
170
+ // C (no edges, inDegree 0) lands in layer 0 alongside start node
171
+ // but gets a 40px vertical offset since it's not the start node
172
+ expect(result['C'].top).to.equal(40);
173
+ });
174
+ it('positions all nodes with non-negative coordinates', () => {
175
+ const nodes = [
176
+ makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),
177
+ makeNode('B', [{ destination_uuid: 'D' }]),
178
+ makeNode('C', [{ destination_uuid: 'D' }]),
179
+ makeNode('D', [])
180
+ ];
181
+ const nodeUIs = {
182
+ A: { position: { left: 0, top: 0 } },
183
+ B: { position: { left: 0, top: 200 } },
184
+ C: { position: { left: 300, top: 200 } },
185
+ D: { position: { left: 150, top: 400 } }
186
+ };
187
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
188
+ for (const uuid of Object.keys(result)) {
189
+ expect(result[uuid].left).to.be.at.least(0);
190
+ expect(result[uuid].top).to.be.at.least(0);
191
+ }
192
+ });
193
+ it('uses different node sizes for layout', () => {
194
+ // A -> B -> C, B is tall
195
+ const nodes = [
196
+ makeNode('A', [{ destination_uuid: 'B' }]),
197
+ makeNode('B', [{ destination_uuid: 'C' }]),
198
+ makeNode('C', [])
199
+ ];
200
+ const nodeUIs = {
201
+ A: { position: { left: 0, top: 0 } },
202
+ B: { position: { left: 0, top: 200 } },
203
+ C: { position: { left: 0, top: 500 } }
204
+ };
205
+ const getSize = (uuid) => {
206
+ if (uuid === 'B')
207
+ return { width: 200, height: 300 };
208
+ return { width: 200, height: 100 };
209
+ };
210
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', getSize);
211
+ // Gap between B and C should account for B's height of 300
212
+ const bBottom = result['B'].top + 300;
213
+ expect(result['C'].top).to.be.greaterThanOrEqual(bBottom);
214
+ });
215
+ it('handles a complex flow with self-loops', () => {
216
+ // A -> B, B -> B (self-loop), B -> C
217
+ const nodes = [
218
+ makeNode('A', [{ destination_uuid: 'B' }]),
219
+ makeNode('B', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),
220
+ makeNode('C', [])
221
+ ];
222
+ const nodeUIs = {
223
+ A: { position: { left: 0, top: 0 } },
224
+ B: { position: { left: 0, top: 200 } },
225
+ C: { position: { left: 0, top: 400 } }
226
+ };
227
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
228
+ expect(result['A'].top).to.equal(0);
229
+ expect(result['B'].top).to.be.greaterThan(result['A'].top);
230
+ expect(result['C'].top).to.be.greaterThan(result['B'].top);
231
+ });
232
+ it('orders sibling nodes using barycenter heuristic', () => {
233
+ // A -> C, B -> D, A and B on layer 0
234
+ // With A first, C should come before D
235
+ const nodes = [
236
+ makeNode('A', [{ destination_uuid: 'C' }]),
237
+ makeNode('B', [{ destination_uuid: 'D' }]),
238
+ makeNode('C', []),
239
+ makeNode('D', [])
240
+ ];
241
+ const nodeUIs = {
242
+ A: { position: { left: 0, top: 0 } },
243
+ B: { position: { left: 300, top: 0 } },
244
+ C: { position: { left: 0, top: 200 } },
245
+ D: { position: { left: 300, top: 200 } }
246
+ };
247
+ // A and B are both roots (no parents), so both in layer 0
248
+ // But A is the start node
249
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
250
+ // C should be left of D (follows parent order)
251
+ expect(result['C'].left).to.be.lessThan(result['D'].left);
252
+ });
253
+ it('non-start nodes in first layer are offset down', () => {
254
+ // A and B both in layer 0 (B unreachable from A but has no parents)
255
+ const nodes = [makeNode('A', []), makeNode('B', [])];
256
+ const nodeUIs = {
257
+ A: { position: { left: 0, top: 0 } },
258
+ B: { position: { left: 300, top: 0 } }
259
+ };
260
+ // B will end up on a later layer since it's unreachable from A
261
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
262
+ // Start node A should be at top=0
263
+ expect(result['A'].top).to.equal(0);
264
+ });
265
+ it('wraps siblings to new rows when exceeding max width', () => {
266
+ // A -> B..H: 7 children at 200px each = 200*7 + 60*6 = 1760px
267
+ // Should split into rows of 4 (980px) and 3 (720px)
268
+ const childIds = ['B', 'C', 'D', 'E', 'F', 'G', 'H'];
269
+ const nodes = [
270
+ makeNode('A', childIds.map((id) => ({ destination_uuid: id }))),
271
+ ...childIds.map((id) => makeNode(id, []))
272
+ ];
273
+ const nodeUIs = { A: { position: { left: 0, top: 0 } } };
274
+ childIds.forEach((id, i) => {
275
+ nodeUIs[id] = { position: { left: i * 260, top: 200 } };
276
+ });
277
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
278
+ // All children should be below A
279
+ for (const id of childIds) {
280
+ expect(result[id].top).to.be.greaterThan(result['A'].top);
281
+ }
282
+ // Children should span multiple rows (not all the same top)
283
+ const tops = new Set(childIds.map((id) => result[id].top));
284
+ expect(tops.size).to.be.greaterThan(1);
285
+ // Each visual row should fit within 1200px
286
+ const rowsByTop = new Map();
287
+ for (const id of childIds) {
288
+ const t = result[id].top;
289
+ const row = rowsByTop.get(t) || [];
290
+ row.push(id);
291
+ rowsByTop.set(t, row);
292
+ }
293
+ for (const [, row] of rowsByTop) {
294
+ const minLeft = Math.min(...row.map((id) => result[id].left));
295
+ const maxRight = Math.max(...row.map((id) => result[id].left + 200));
296
+ expect(maxRight - minLeft).to.be.at.most(1200);
297
+ }
298
+ });
299
+ it('handles wider flows with multiple branches', () => {
300
+ // A -> B, A -> C, A -> D
301
+ const nodes = [
302
+ makeNode('A', [
303
+ { destination_uuid: 'B' },
304
+ { destination_uuid: 'C' },
305
+ { destination_uuid: 'D' }
306
+ ]),
307
+ makeNode('B', []),
308
+ makeNode('C', []),
309
+ makeNode('D', [])
310
+ ];
311
+ const nodeUIs = {
312
+ A: { position: { left: 0, top: 0 } },
313
+ B: { position: { left: 0, top: 200 } },
314
+ C: { position: { left: 300, top: 200 } },
315
+ D: { position: { left: 600, top: 200 } }
316
+ };
317
+ const result = calculateLayeredLayout(nodes, nodeUIs, 'A', constantSize(200, 100));
318
+ // All three children should be on the same layer
319
+ expect(result['B'].top).to.equal(result['C'].top);
320
+ expect(result['C'].top).to.equal(result['D'].top);
321
+ // They should be spread horizontally with gaps
322
+ const lefts = [result['B'].left, result['C'].left, result['D'].left].sort((a, b) => a - b);
323
+ expect(lefts[1]).to.be.greaterThan(lefts[0]);
324
+ expect(lefts[2]).to.be.greaterThan(lefts[1]);
325
+ });
326
+ });
327
+ describe('placeStickyNotes', () => {
328
+ const makeSticky = (left, top) => ({
329
+ position: { left, top },
330
+ title: '',
331
+ body: '',
332
+ color: 'yellow'
333
+ });
334
+ it('returns empty object when no stickies', () => {
335
+ const result = placeStickyNotes({}, { A: { left: 0, top: 0 } }, { A: { left: 0, top: 0 } }, new Map([['A', { width: 200, height: 100 }]]), new Map(), 'A');
336
+ expect(Object.keys(result)).to.have.length(0);
337
+ });
338
+ it('returns empty object when no nodes', () => {
339
+ const result = placeStickyNotes({ s1: makeSticky(100, 100) }, {}, {}, new Map(), new Map(), 'A');
340
+ expect(Object.keys(result)).to.have.length(0);
341
+ });
342
+ it('places sticky to the right of its closest node', () => {
343
+ const stickies = { s1: makeSticky(250, 0) }; // right of node at (0,0)
344
+ const oldPositions = { A: { left: 0, top: 0 } };
345
+ const newPositions = { A: { left: 0, top: 0 } };
346
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
347
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
348
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
349
+ expect(result['s1']).to.not.be.undefined;
350
+ // Should be placed to the right of node A
351
+ expect(result['s1'].left).to.be.greaterThanOrEqual(200);
352
+ });
353
+ it('places sticky to the left when it was originally left', () => {
354
+ // Sticky was to the left of node B (not start node)
355
+ const stickies = { s1: makeSticky(0, 0) }; // left of node at (300, 0)
356
+ const oldPositions = {
357
+ A: { left: 0, top: 0 },
358
+ B: { left: 300, top: 200 }
359
+ };
360
+ const newPositions = {
361
+ A: { left: 0, top: 0 },
362
+ B: { left: 300, top: 200 }
363
+ };
364
+ const nodeSizes = new Map([
365
+ ['A', { width: 200, height: 100 }],
366
+ ['B', { width: 200, height: 100 }]
367
+ ]);
368
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
369
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
370
+ expect(result['s1']).to.not.be.undefined;
371
+ });
372
+ it('redirects left-of-start sticky to the right side', () => {
373
+ // Sticky was to the left of the start node — should be placed to the right
374
+ const stickies = { s1: makeSticky(-200, 0) }; // left of start node at (0,0)
375
+ const oldPositions = { A: { left: 0, top: 0 } };
376
+ const newPositions = { A: { left: 0, top: 0 } };
377
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
378
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
379
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
380
+ expect(result['s1']).to.not.be.undefined;
381
+ // Should be placed to the right (not left) of the start node
382
+ expect(result['s1'].left).to.be.greaterThanOrEqual(200);
383
+ });
384
+ it('snaps sticky positions to grid', () => {
385
+ const stickies = { s1: makeSticky(250, 5) };
386
+ const oldPositions = { A: { left: 0, top: 0 } };
387
+ const newPositions = { A: { left: 0, top: 0 } };
388
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
389
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
390
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
391
+ expect(result['s1'].left % 20).to.equal(0);
392
+ expect(result['s1'].top % 20).to.equal(0);
393
+ });
394
+ it('assigns sticky to closest node', () => {
395
+ // s1 is at (310, 200), closer to B at (300, 200) than A at (0, 0)
396
+ const stickies = { s1: makeSticky(310, 200) };
397
+ const oldPositions = {
398
+ A: { left: 0, top: 0 },
399
+ B: { left: 300, top: 200 }
400
+ };
401
+ const newPositions = {
402
+ A: { left: 0, top: 0 },
403
+ B: { left: 300, top: 200 }
404
+ };
405
+ const nodeSizes = new Map([
406
+ ['A', { width: 200, height: 100 }],
407
+ ['B', { width: 200, height: 100 }]
408
+ ]);
409
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
410
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
411
+ expect(result['s1']).to.not.be.undefined;
412
+ // Should be placed near node B, not node A
413
+ expect(result['s1'].left).to.be.greaterThan(200);
414
+ });
415
+ it('nudges stickies to avoid collisions', () => {
416
+ // Two stickies both closest to node A, both on the right side
417
+ const stickies = {
418
+ s1: makeSticky(250, 0),
419
+ s2: makeSticky(260, 0)
420
+ };
421
+ const oldPositions = { A: { left: 0, top: 0 } };
422
+ const newPositions = { A: { left: 0, top: 0 } };
423
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
424
+ const stickySizes = new Map([
425
+ ['s1', { width: 182, height: 100 }],
426
+ ['s2', { width: 182, height: 100 }]
427
+ ]);
428
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
429
+ // Both stickies should be placed
430
+ expect(result['s1']).to.not.be.undefined;
431
+ expect(result['s2']).to.not.be.undefined;
432
+ // If they have the same left, they should be vertically separated
433
+ if (result['s1'].left === result['s2'].left) {
434
+ expect(result['s1'].top).to.not.equal(result['s2'].top);
435
+ }
436
+ });
437
+ it('skips stickies without position', () => {
438
+ const stickies = {
439
+ s1: { title: '', body: '', color: 'yellow' }
440
+ };
441
+ const oldPositions = { A: { left: 0, top: 0 } };
442
+ const newPositions = { A: { left: 0, top: 0 } };
443
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
444
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
445
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
446
+ // Sticky without position should be skipped
447
+ expect(result['s1']).to.be.undefined;
448
+ });
449
+ it('ensures non-negative coordinates for stickies', () => {
450
+ const stickies = { s1: makeSticky(250, 0) };
451
+ const oldPositions = { A: { left: 0, top: 0 } };
452
+ const newPositions = { A: { left: 0, top: 0 } };
453
+ const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);
454
+ const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);
455
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
456
+ expect(result['s1'].left).to.be.at.least(0);
457
+ expect(result['s1'].top).to.be.at.least(0);
458
+ });
459
+ it('uses default sizes when not provided', () => {
460
+ const stickies = { s1: makeSticky(250, 0) };
461
+ const oldPositions = { A: { left: 0, top: 0 } };
462
+ const newPositions = { A: { left: 0, top: 0 } };
463
+ const nodeSizes = new Map();
464
+ const stickySizes = new Map();
465
+ const result = placeStickyNotes(stickies, oldPositions, newPositions, nodeSizes, stickySizes, 'A');
466
+ expect(result['s1']).to.not.be.undefined;
467
+ expect(result['s1'].left).to.be.at.least(0);
468
+ expect(result['s1'].top).to.be.at.least(0);
469
+ });
470
+ });
471
+ });
472
+ //# sourceMappingURL=temba-flow-reflow.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temba-flow-reflow.test.js","sourceRoot":"","sources":["../../test/temba-flow-reflow.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { calculateLayeredLayout, placeStickyNotes } from '../src/flow/reflow';\nimport { Node, StickyNote } from '../src/store/flow-definition';\n\n// Helper to create a minimal Node with exits\nfunction makeNode(uuid: string, exits: { destination_uuid?: string }[]): Node {\n return {\n uuid,\n actions: [],\n exits: exits.map((e, i) => ({\n uuid: `${uuid}-exit-${i}`,\n destination_uuid: e.destination_uuid\n }))\n };\n}\n\n// Helper to return a fixed size for all nodes\nfunction constantSize(width: number, height: number) {\n return () => ({ width, height });\n}\n\ndescribe('Reflow Layout', () => {\n describe('calculateLayeredLayout', () => {\n it('returns empty object for empty nodes', () => {\n const result = calculateLayeredLayout(\n [],\n {},\n 'start',\n constantSize(200, 100)\n );\n expect(Object.keys(result)).to.have.length(0);\n });\n\n it('places a single node at the origin', () => {\n const nodes = [makeNode('A', [])];\n const nodeUIs = { A: { position: { left: 500, top: 500 } } };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n expect(result['A']).to.not.be.undefined;\n expect(result['A'].left).to.equal(0);\n expect(result['A'].top).to.equal(0);\n });\n\n it('places a linear chain vertically', () => {\n // A -> B -> C\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', [{ destination_uuid: 'C' }]),\n makeNode('C', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 0, top: 400 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // All should be at left=0 (centered under parent which is at 0)\n // A at layer 0, B at layer 1, C at layer 2\n expect(result['A'].top).to.equal(0);\n expect(result['B'].top).to.be.greaterThan(result['A'].top);\n expect(result['C'].top).to.be.greaterThan(result['B'].top);\n });\n\n it('places siblings at the same vertical level', () => {\n // A -> B, A -> C\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),\n makeNode('B', []),\n makeNode('C', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 300, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // B and C should be on the same layer (same top)\n expect(result['B'].top).to.equal(result['C'].top);\n expect(result['B'].top).to.be.greaterThan(result['A'].top);\n // B and C should be side by side\n expect(result['B'].left).to.not.equal(result['C'].left);\n });\n\n it('snaps all positions to grid (multiples of 20)', () => {\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n for (const uuid of Object.keys(result)) {\n expect(result[uuid].left % 20).to.equal(0);\n expect(result[uuid].top % 20).to.equal(0);\n }\n });\n\n it('handles cycles (back-edges) without infinite loops', () => {\n // A -> B -> A (cycle)\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', [{ destination_uuid: 'A' }])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n expect(result['A']).to.not.be.undefined;\n expect(result['B']).to.not.be.undefined;\n // A should be above B (layer 0 vs layer 1)\n expect(result['A'].top).to.be.lessThan(result['B'].top);\n });\n\n it('handles diamond-shaped flows (merge nodes)', () => {\n // A -> B, A -> C, B -> D, C -> D\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),\n makeNode('B', [{ destination_uuid: 'D' }]),\n makeNode('C', [{ destination_uuid: 'D' }]),\n makeNode('D', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 300, top: 200 } },\n D: { position: { left: 150, top: 400 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // D should be below both B and C (longest path assignment)\n expect(result['D'].top).to.be.greaterThan(result['B'].top);\n expect(result['D'].top).to.be.greaterThan(result['C'].top);\n // B and C on same layer\n expect(result['B'].top).to.equal(result['C'].top);\n });\n\n it('handles deduplicated exits (same destination from multiple exits)', () => {\n // A has two exits both pointing to B\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'B' }]),\n makeNode('B', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n expect(result['A']).to.not.be.undefined;\n expect(result['B']).to.not.be.undefined;\n expect(result['B'].top).to.be.greaterThan(result['A'].top);\n });\n\n it('handles exits with no destination', () => {\n const nodes = [\n makeNode('A', [\n { destination_uuid: undefined },\n { destination_uuid: 'B' }\n ]),\n makeNode('B', [{ destination_uuid: undefined }])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n expect(result['A']).to.not.be.undefined;\n expect(result['B']).to.not.be.undefined;\n });\n\n it('handles disconnected nodes', () => {\n // A -> B, C is disconnected (no edges at all)\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', []),\n makeNode('C', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 300, top: 300 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // All nodes should receive positions\n expect(result['A']).to.not.be.undefined;\n expect(result['B']).to.not.be.undefined;\n expect(result['C']).to.not.be.undefined;\n // C (no edges, inDegree 0) lands in layer 0 alongside start node\n // but gets a 40px vertical offset since it's not the start node\n expect(result['C'].top).to.equal(40);\n });\n\n it('positions all nodes with non-negative coordinates', () => {\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),\n makeNode('B', [{ destination_uuid: 'D' }]),\n makeNode('C', [{ destination_uuid: 'D' }]),\n makeNode('D', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 300, top: 200 } },\n D: { position: { left: 150, top: 400 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n for (const uuid of Object.keys(result)) {\n expect(result[uuid].left).to.be.at.least(0);\n expect(result[uuid].top).to.be.at.least(0);\n }\n });\n\n it('uses different node sizes for layout', () => {\n // A -> B -> C, B is tall\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', [{ destination_uuid: 'C' }]),\n makeNode('C', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 0, top: 500 } }\n };\n const getSize = (uuid: string) => {\n if (uuid === 'B') return { width: 200, height: 300 };\n return { width: 200, height: 100 };\n };\n const result = calculateLayeredLayout(nodes, nodeUIs, 'A', getSize);\n\n // Gap between B and C should account for B's height of 300\n const bBottom = result['B'].top + 300;\n expect(result['C'].top).to.be.greaterThanOrEqual(bBottom);\n });\n\n it('handles a complex flow with self-loops', () => {\n // A -> B, B -> B (self-loop), B -> C\n const nodes = [\n makeNode('A', [{ destination_uuid: 'B' }]),\n makeNode('B', [{ destination_uuid: 'B' }, { destination_uuid: 'C' }]),\n makeNode('C', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 0, top: 400 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n expect(result['A'].top).to.equal(0);\n expect(result['B'].top).to.be.greaterThan(result['A'].top);\n expect(result['C'].top).to.be.greaterThan(result['B'].top);\n });\n\n it('orders sibling nodes using barycenter heuristic', () => {\n // A -> C, B -> D, A and B on layer 0\n // With A first, C should come before D\n const nodes = [\n makeNode('A', [{ destination_uuid: 'C' }]),\n makeNode('B', [{ destination_uuid: 'D' }]),\n makeNode('C', []),\n makeNode('D', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 300, top: 0 } },\n C: { position: { left: 0, top: 200 } },\n D: { position: { left: 300, top: 200 } }\n };\n // A and B are both roots (no parents), so both in layer 0\n // But A is the start node\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // C should be left of D (follows parent order)\n expect(result['C'].left).to.be.lessThan(result['D'].left);\n });\n\n it('non-start nodes in first layer are offset down', () => {\n // A and B both in layer 0 (B unreachable from A but has no parents)\n const nodes = [makeNode('A', []), makeNode('B', [])];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 300, top: 0 } }\n };\n // B will end up on a later layer since it's unreachable from A\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // Start node A should be at top=0\n expect(result['A'].top).to.equal(0);\n });\n\n it('wraps siblings to new rows when exceeding max width', () => {\n // A -> B..H: 7 children at 200px each = 200*7 + 60*6 = 1760px\n // Should split into rows of 4 (980px) and 3 (720px)\n const childIds = ['B', 'C', 'D', 'E', 'F', 'G', 'H'];\n const nodes = [\n makeNode(\n 'A',\n childIds.map((id) => ({ destination_uuid: id }))\n ),\n ...childIds.map((id) => makeNode(id, []))\n ];\n const nodeUIs: Record<string, { position: { left: number; top: number } }> =\n { A: { position: { left: 0, top: 0 } } };\n childIds.forEach((id, i) => {\n nodeUIs[id] = { position: { left: i * 260, top: 200 } };\n });\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // All children should be below A\n for (const id of childIds) {\n expect(result[id].top).to.be.greaterThan(result['A'].top);\n }\n\n // Children should span multiple rows (not all the same top)\n const tops = new Set(childIds.map((id) => result[id].top));\n expect(tops.size).to.be.greaterThan(1);\n\n // Each visual row should fit within 1200px\n const rowsByTop = new Map<number, string[]>();\n for (const id of childIds) {\n const t = result[id].top;\n const row = rowsByTop.get(t) || [];\n row.push(id);\n rowsByTop.set(t, row);\n }\n for (const [, row] of rowsByTop) {\n const minLeft = Math.min(...row.map((id) => result[id].left));\n const maxRight = Math.max(...row.map((id) => result[id].left + 200));\n expect(maxRight - minLeft).to.be.at.most(1200);\n }\n });\n\n it('handles wider flows with multiple branches', () => {\n // A -> B, A -> C, A -> D\n const nodes = [\n makeNode('A', [\n { destination_uuid: 'B' },\n { destination_uuid: 'C' },\n { destination_uuid: 'D' }\n ]),\n makeNode('B', []),\n makeNode('C', []),\n makeNode('D', [])\n ];\n const nodeUIs = {\n A: { position: { left: 0, top: 0 } },\n B: { position: { left: 0, top: 200 } },\n C: { position: { left: 300, top: 200 } },\n D: { position: { left: 600, top: 200 } }\n };\n const result = calculateLayeredLayout(\n nodes,\n nodeUIs,\n 'A',\n constantSize(200, 100)\n );\n\n // All three children should be on the same layer\n expect(result['B'].top).to.equal(result['C'].top);\n expect(result['C'].top).to.equal(result['D'].top);\n\n // They should be spread horizontally with gaps\n const lefts = [result['B'].left, result['C'].left, result['D'].left].sort(\n (a, b) => a - b\n );\n expect(lefts[1]).to.be.greaterThan(lefts[0]);\n expect(lefts[2]).to.be.greaterThan(lefts[1]);\n });\n });\n\n describe('placeStickyNotes', () => {\n const makeSticky = (left: number, top: number): StickyNote => ({\n position: { left, top },\n title: '',\n body: '',\n color: 'yellow'\n });\n\n it('returns empty object when no stickies', () => {\n const result = placeStickyNotes(\n {},\n { A: { left: 0, top: 0 } },\n { A: { left: 0, top: 0 } },\n new Map([['A', { width: 200, height: 100 }]]),\n new Map(),\n 'A'\n );\n expect(Object.keys(result)).to.have.length(0);\n });\n\n it('returns empty object when no nodes', () => {\n const result = placeStickyNotes(\n { s1: makeSticky(100, 100) },\n {},\n {},\n new Map(),\n new Map(),\n 'A'\n );\n expect(Object.keys(result)).to.have.length(0);\n });\n\n it('places sticky to the right of its closest node', () => {\n const stickies = { s1: makeSticky(250, 0) }; // right of node at (0,0)\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1']).to.not.be.undefined;\n // Should be placed to the right of node A\n expect(result['s1'].left).to.be.greaterThanOrEqual(200);\n });\n\n it('places sticky to the left when it was originally left', () => {\n // Sticky was to the left of node B (not start node)\n const stickies = { s1: makeSticky(0, 0) }; // left of node at (300, 0)\n const oldPositions = {\n A: { left: 0, top: 0 },\n B: { left: 300, top: 200 }\n };\n const newPositions = {\n A: { left: 0, top: 0 },\n B: { left: 300, top: 200 }\n };\n const nodeSizes = new Map([\n ['A', { width: 200, height: 100 }],\n ['B', { width: 200, height: 100 }]\n ]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1']).to.not.be.undefined;\n });\n\n it('redirects left-of-start sticky to the right side', () => {\n // Sticky was to the left of the start node — should be placed to the right\n const stickies = { s1: makeSticky(-200, 0) }; // left of start node at (0,0)\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1']).to.not.be.undefined;\n // Should be placed to the right (not left) of the start node\n expect(result['s1'].left).to.be.greaterThanOrEqual(200);\n });\n\n it('snaps sticky positions to grid', () => {\n const stickies = { s1: makeSticky(250, 5) };\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1'].left % 20).to.equal(0);\n expect(result['s1'].top % 20).to.equal(0);\n });\n\n it('assigns sticky to closest node', () => {\n // s1 is at (310, 200), closer to B at (300, 200) than A at (0, 0)\n const stickies = { s1: makeSticky(310, 200) };\n const oldPositions = {\n A: { left: 0, top: 0 },\n B: { left: 300, top: 200 }\n };\n const newPositions = {\n A: { left: 0, top: 0 },\n B: { left: 300, top: 200 }\n };\n const nodeSizes = new Map([\n ['A', { width: 200, height: 100 }],\n ['B', { width: 200, height: 100 }]\n ]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1']).to.not.be.undefined;\n // Should be placed near node B, not node A\n expect(result['s1'].left).to.be.greaterThan(200);\n });\n\n it('nudges stickies to avoid collisions', () => {\n // Two stickies both closest to node A, both on the right side\n const stickies = {\n s1: makeSticky(250, 0),\n s2: makeSticky(260, 0)\n };\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([\n ['s1', { width: 182, height: 100 }],\n ['s2', { width: 182, height: 100 }]\n ]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n // Both stickies should be placed\n expect(result['s1']).to.not.be.undefined;\n expect(result['s2']).to.not.be.undefined;\n\n // If they have the same left, they should be vertically separated\n if (result['s1'].left === result['s2'].left) {\n expect(result['s1'].top).to.not.equal(result['s2'].top);\n }\n });\n\n it('skips stickies without position', () => {\n const stickies = {\n s1: { title: '', body: '', color: 'yellow' as const } as StickyNote\n };\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n // Sticky without position should be skipped\n expect(result['s1']).to.be.undefined;\n });\n\n it('ensures non-negative coordinates for stickies', () => {\n const stickies = { s1: makeSticky(250, 0) };\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map([['A', { width: 200, height: 100 }]]);\n const stickySizes = new Map([['s1', { width: 182, height: 100 }]]);\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1'].left).to.be.at.least(0);\n expect(result['s1'].top).to.be.at.least(0);\n });\n\n it('uses default sizes when not provided', () => {\n const stickies = { s1: makeSticky(250, 0) };\n const oldPositions = { A: { left: 0, top: 0 } };\n const newPositions = { A: { left: 0, top: 0 } };\n const nodeSizes = new Map<string, { width: number; height: number }>();\n const stickySizes = new Map<string, { width: number; height: number }>();\n\n const result = placeStickyNotes(\n stickies,\n oldPositions,\n newPositions,\n nodeSizes,\n stickySizes,\n 'A'\n );\n\n expect(result['s1']).to.not.be.undefined;\n expect(result['s1'].left).to.be.at.least(0);\n expect(result['s1'].top).to.be.at.least(0);\n });\n });\n});\n"]}