@nyaruka/temba-components 0.138.6 → 0.140.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/CHANGELOG.md +26 -0
  4. package/demo/data/flows/sample-flow.json +24 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +1112 -882
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +10 -7
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/Dropdown.js +3 -1
  18. package/out-tsc/src/display/Dropdown.js.map +1 -1
  19. package/out-tsc/src/display/FloatingTab.js +25 -32
  20. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  21. package/out-tsc/src/display/Thumbnail.js +163 -5
  22. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  23. package/out-tsc/src/flow/CanvasMenu.js +5 -3
  24. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  25. package/out-tsc/src/flow/CanvasNode.js +70 -29
  26. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  27. package/out-tsc/src/flow/Editor.js +290 -239
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/NodeEditor.js +118 -10
  30. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  31. package/out-tsc/src/flow/Plumber.js +757 -403
  32. package/out-tsc/src/flow/Plumber.js.map +1 -1
  33. package/out-tsc/src/flow/StickyNote.js +13 -4
  34. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  35. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  36. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  37. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  38. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  39. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  40. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  41. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  42. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  43. package/out-tsc/src/flow/config.js +11 -3
  44. package/out-tsc/src/flow/config.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  46. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  48. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  49. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  50. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  51. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  52. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  53. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  54. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  55. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  56. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  57. package/out-tsc/src/flow/operators.js +21 -5
  58. package/out-tsc/src/flow/operators.js.map +1 -1
  59. package/out-tsc/src/flow/types.js.map +1 -1
  60. package/out-tsc/src/flow/utils.js +213 -65
  61. package/out-tsc/src/flow/utils.js.map +1 -1
  62. package/out-tsc/src/form/ArrayEditor.js +4 -2
  63. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  64. package/out-tsc/src/form/FieldRenderer.js +49 -0
  65. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  66. package/out-tsc/src/interfaces.js +2 -0
  67. package/out-tsc/src/interfaces.js.map +1 -1
  68. package/out-tsc/src/layout/Dialog.js +52 -7
  69. package/out-tsc/src/layout/Dialog.js.map +1 -1
  70. package/out-tsc/src/list/TicketList.js +4 -1
  71. package/out-tsc/src/list/TicketList.js.map +1 -1
  72. package/out-tsc/src/live/TembaChart.js.map +1 -1
  73. package/out-tsc/src/locales/es.js +5 -5
  74. package/out-tsc/src/locales/es.js.map +1 -1
  75. package/out-tsc/src/locales/fr.js +5 -5
  76. package/out-tsc/src/locales/fr.js.map +1 -1
  77. package/out-tsc/src/locales/locale-codes.js +2 -11
  78. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  79. package/out-tsc/src/locales/pt.js +5 -5
  80. package/out-tsc/src/locales/pt.js.map +1 -1
  81. package/out-tsc/src/simulator/Simulator.js +10 -3
  82. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  83. package/out-tsc/src/store/AppState.js +89 -3
  84. package/out-tsc/src/store/AppState.js.map +1 -1
  85. package/out-tsc/test/actions/play_audio.test.js +118 -0
  86. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  87. package/out-tsc/test/actions/say_msg.test.js +158 -0
  88. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  89. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  90. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  91. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  92. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  93. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  94. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  95. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  96. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  97. package/out-tsc/test/temba-floating-tab.test.js +4 -6
  98. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  99. package/out-tsc/test/temba-flow-collision.test.js +473 -220
  100. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  101. package/out-tsc/test/temba-flow-editor.test.js +0 -2
  102. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  103. package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
  104. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  105. package/out-tsc/test/temba-flow-plumber.test.js +102 -93
  106. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  107. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  108. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  109. package/package.json +1 -1
  110. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  111. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  112. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  113. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  114. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  115. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  116. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  117. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  118. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  119. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  120. package/screenshots/truth/editor/router.png +0 -0
  121. package/screenshots/truth/editor/wait.png +0 -0
  122. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  123. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  124. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  125. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  134. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  135. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  136. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  142. package/src/display/Chat.ts +13 -7
  143. package/src/display/Dropdown.ts +3 -1
  144. package/src/display/FloatingTab.ts +24 -33
  145. package/src/display/Thumbnail.ts +162 -2
  146. package/src/flow/CanvasMenu.ts +8 -3
  147. package/src/flow/CanvasNode.ts +75 -30
  148. package/src/flow/Editor.ts +336 -288
  149. package/src/flow/NodeEditor.ts +137 -9
  150. package/src/flow/Plumber.ts +1011 -457
  151. package/src/flow/StickyNote.ts +14 -4
  152. package/src/flow/actions/audio-player.ts +127 -0
  153. package/src/flow/actions/enter_flow.ts +44 -0
  154. package/src/flow/actions/play_audio.ts +64 -5
  155. package/src/flow/actions/say_msg.ts +94 -4
  156. package/src/flow/config.ts +11 -3
  157. package/src/flow/nodes/shared-rules.ts +1 -1
  158. package/src/flow/nodes/terminal.ts +9 -0
  159. package/src/flow/nodes/wait_for_audio.ts +88 -0
  160. package/src/flow/nodes/wait_for_dial.ts +176 -0
  161. package/src/flow/nodes/wait_for_digits.ts +86 -2
  162. package/src/flow/nodes/wait_for_menu.ts +209 -3
  163. package/src/flow/operators.ts +23 -5
  164. package/src/flow/types.ts +23 -1
  165. package/src/flow/utils.ts +238 -81
  166. package/src/form/ArrayEditor.ts +4 -2
  167. package/src/form/FieldRenderer.ts +64 -1
  168. package/src/interfaces.ts +3 -1
  169. package/src/layout/Dialog.ts +53 -7
  170. package/src/list/TicketList.ts +4 -1
  171. package/src/live/TembaChart.ts +1 -1
  172. package/src/locales/es.ts +13 -18
  173. package/src/locales/fr.ts +13 -18
  174. package/src/locales/locale-codes.ts +2 -11
  175. package/src/locales/pt.ts +13 -18
  176. package/src/simulator/Simulator.ts +13 -3
  177. package/src/store/AppState.ts +105 -1
  178. package/src/store/flow-definition.d.ts +2 -0
  179. package/test/actions/play_audio.test.ts +155 -0
  180. package/test/actions/say_msg.test.ts +196 -0
  181. package/test/nodes/wait_for_audio.test.ts +182 -0
  182. package/test/nodes/wait_for_dial.test.ts +382 -0
  183. package/test/nodes/wait_for_digits.test.ts +233 -109
  184. package/test/nodes/wait_for_menu.test.ts +383 -0
  185. package/test/temba-floating-tab.test.ts +4 -6
  186. package/test/temba-flow-collision.test.ts +495 -293
  187. package/test/temba-flow-editor.test.ts +0 -2
  188. package/test/temba-flow-plumber-connections.test.ts +97 -97
  189. package/test/temba-flow-plumber.test.ts +116 -103
  190. package/test/temba-node-type-selector.test.ts +6 -6
  191. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -0,0 +1,383 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { wait_for_menu } from '../../src/flow/nodes/wait_for_menu';
3
+ import { Node } from '../../src/store/flow-definition';
4
+ import { NodeTest } from '../NodeHelper';
5
+
6
+ /**
7
+ * Test suite for the wait_for_menu node configuration.
8
+ */
9
+ describe('wait_for_menu node config', () => {
10
+ const helper = new NodeTest(wait_for_menu, 'wait_for_menu');
11
+
12
+ describe('basic properties', () => {
13
+ helper.testBasicProperties();
14
+
15
+ it('has correct name', () => {
16
+ expect(wait_for_menu.name).to.equal('Wait for Menu');
17
+ });
18
+
19
+ it('has correct type', () => {
20
+ expect(wait_for_menu.type).to.equal('wait_for_menu');
21
+ });
22
+
23
+ it('is voice-only', () => {
24
+ expect(wait_for_menu.flowTypes).to.deep.equal(['voice']);
25
+ });
26
+
27
+ it('has form with 10 digit fields plus result_name', () => {
28
+ expect(wait_for_menu.form).to.exist;
29
+ for (const d of ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']) {
30
+ expect(wait_for_menu.form![`digit_${d}`]).to.exist;
31
+ }
32
+ expect(wait_for_menu.form!.result_name).to.exist;
33
+ });
34
+ });
35
+
36
+ describe('node scenarios', () => {
37
+ it('renders menu with filled digits', async () => {
38
+ await helper.testNode(
39
+ {
40
+ uuid: 'test-menu-node-1',
41
+ actions: [],
42
+ router: {
43
+ type: 'switch',
44
+ operand: '@input.text',
45
+ wait: {
46
+ type: 'msg',
47
+ hint: { type: 'digits', count: 1 }
48
+ },
49
+ result_name: 'menu_choice',
50
+ default_category_uuid: 'other-cat',
51
+ cases: [
52
+ {
53
+ uuid: 'case-1',
54
+ type: 'has_number_eq',
55
+ arguments: ['1'],
56
+ category_uuid: 'sales-cat'
57
+ },
58
+ {
59
+ uuid: 'case-2',
60
+ type: 'has_number_eq',
61
+ arguments: ['2'],
62
+ category_uuid: 'support-cat'
63
+ },
64
+ {
65
+ uuid: 'case-0',
66
+ type: 'has_number_eq',
67
+ arguments: ['0'],
68
+ category_uuid: 'operator-cat'
69
+ }
70
+ ],
71
+ categories: [
72
+ {
73
+ uuid: 'sales-cat',
74
+ name: 'Sales',
75
+ exit_uuid: 'sales-exit'
76
+ },
77
+ {
78
+ uuid: 'support-cat',
79
+ name: 'Support',
80
+ exit_uuid: 'support-exit'
81
+ },
82
+ {
83
+ uuid: 'operator-cat',
84
+ name: 'Operator',
85
+ exit_uuid: 'operator-exit'
86
+ },
87
+ {
88
+ uuid: 'other-cat',
89
+ name: 'Other',
90
+ exit_uuid: 'other-exit'
91
+ }
92
+ ]
93
+ },
94
+ exits: [
95
+ { uuid: 'sales-exit', destination_uuid: null },
96
+ { uuid: 'support-exit', destination_uuid: null },
97
+ { uuid: 'operator-exit', destination_uuid: null },
98
+ { uuid: 'other-exit', destination_uuid: null }
99
+ ]
100
+ } as Node,
101
+ { type: 'wait_for_menu' },
102
+ 'menu-with-digits'
103
+ );
104
+ });
105
+ });
106
+
107
+ describe('data transformation', () => {
108
+ it('converts node to form data', () => {
109
+ const node: Node = {
110
+ uuid: 'test-node',
111
+ actions: [],
112
+ router: {
113
+ type: 'switch',
114
+ result_name: 'menu',
115
+ categories: [
116
+ {
117
+ uuid: 'sales-cat',
118
+ name: 'Sales',
119
+ exit_uuid: 'sales-exit'
120
+ },
121
+ {
122
+ uuid: 'support-cat',
123
+ name: 'Support',
124
+ exit_uuid: 'support-exit'
125
+ },
126
+ {
127
+ uuid: 'other-cat',
128
+ name: 'Other',
129
+ exit_uuid: 'other-exit'
130
+ }
131
+ ],
132
+ cases: [
133
+ {
134
+ uuid: 'case-1',
135
+ type: 'has_number_eq',
136
+ arguments: ['1'],
137
+ category_uuid: 'sales-cat'
138
+ },
139
+ {
140
+ uuid: 'case-2',
141
+ type: 'has_number_eq',
142
+ arguments: ['2'],
143
+ category_uuid: 'support-cat'
144
+ }
145
+ ]
146
+ },
147
+ exits: [
148
+ { uuid: 'sales-exit', destination_uuid: null },
149
+ { uuid: 'support-exit', destination_uuid: null },
150
+ { uuid: 'other-exit', destination_uuid: null }
151
+ ]
152
+ };
153
+
154
+ const formData = wait_for_menu.toFormData!(node);
155
+
156
+ expect(formData.uuid).to.equal('test-node');
157
+ expect(formData.digit_1).to.equal('Sales');
158
+ expect(formData.digit_2).to.equal('Support');
159
+ expect(formData.digit_3).to.equal('');
160
+ expect(formData.digit_0).to.equal('');
161
+ expect(formData.result_name).to.equal('menu');
162
+ });
163
+
164
+ it('handles empty menu', () => {
165
+ const node: Node = {
166
+ uuid: 'test-node',
167
+ actions: [],
168
+ router: {
169
+ type: 'switch',
170
+ categories: []
171
+ },
172
+ exits: []
173
+ };
174
+
175
+ const formData = wait_for_menu.toFormData!(node);
176
+
177
+ for (const d of ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']) {
178
+ expect(formData[`digit_${d}`]).to.equal('');
179
+ }
180
+ });
181
+
182
+ it('creates node from form data with filled digits', () => {
183
+ const formData = {
184
+ uuid: 'test-node',
185
+ digit_1: 'Sales',
186
+ digit_2: 'Support',
187
+ digit_3: '',
188
+ digit_4: '',
189
+ digit_5: '',
190
+ digit_6: '',
191
+ digit_7: '',
192
+ digit_8: '',
193
+ digit_9: '',
194
+ digit_0: 'Operator',
195
+ result_name: 'menu_choice'
196
+ };
197
+
198
+ const originalNode: Node = {
199
+ uuid: 'test-node',
200
+ actions: [],
201
+ exits: [],
202
+ router: { type: 'switch', categories: [] }
203
+ };
204
+
205
+ const result = wait_for_menu.fromFormData!(formData, originalNode);
206
+
207
+ // Should have 4 categories: Sales, Support, Operator, Other
208
+ expect(result.router?.categories).to.have.length(4);
209
+ const names = result.router!.categories.map((c) => c.name);
210
+ expect(names).to.deep.equal(['Sales', 'Support', 'Operator', 'Other']);
211
+
212
+ // Should have 3 cases
213
+ expect(result.router?.cases).to.have.length(3);
214
+ expect(result.router!.cases[0].arguments).to.deep.equal(['1']);
215
+ expect(result.router!.cases[1].arguments).to.deep.equal(['2']);
216
+ expect(result.router!.cases[2].arguments).to.deep.equal(['0']);
217
+
218
+ // Check wait config
219
+ expect(result.router?.wait?.type).to.equal('msg');
220
+ expect(result.router?.wait?.hint?.type).to.equal('digits');
221
+ expect(result.router?.wait?.hint?.count).to.equal(1);
222
+
223
+ // Check result name
224
+ expect(result.router?.result_name).to.equal('menu_choice');
225
+
226
+ // 4 exits
227
+ expect(result.exits).to.have.length(4);
228
+ });
229
+
230
+ it('merges duplicate category names', () => {
231
+ const formData = {
232
+ uuid: 'test-node',
233
+ digit_1: 'Sales',
234
+ digit_2: 'Sales', // same category name
235
+ digit_3: '',
236
+ digit_4: '',
237
+ digit_5: '',
238
+ digit_6: '',
239
+ digit_7: '',
240
+ digit_8: '',
241
+ digit_9: '',
242
+ digit_0: '',
243
+ result_name: ''
244
+ };
245
+
246
+ const originalNode: Node = {
247
+ uuid: 'test-node',
248
+ actions: [],
249
+ exits: [],
250
+ router: { type: 'switch', categories: [] }
251
+ };
252
+
253
+ const result = wait_for_menu.fromFormData!(formData, originalNode);
254
+
255
+ // Should have 2 categories: Sales and Other (not duplicate Sales)
256
+ expect(result.router?.categories).to.have.length(2);
257
+ expect(result.router!.categories[0].name).to.equal('Sales');
258
+ expect(result.router!.categories[1].name).to.equal('Other');
259
+
260
+ // Both cases should reference same category
261
+ expect(result.router?.cases).to.have.length(2);
262
+ expect(result.router!.cases[0].category_uuid).to.equal(
263
+ result.router!.cases[1].category_uuid
264
+ );
265
+
266
+ // Should have 2 exits (not 3)
267
+ expect(result.exits).to.have.length(2);
268
+ });
269
+
270
+ it('preserves existing category UUIDs', () => {
271
+ const formData = {
272
+ uuid: 'test-node',
273
+ digit_1: 'Sales',
274
+ digit_2: 'Support',
275
+ digit_3: '',
276
+ digit_4: '',
277
+ digit_5: '',
278
+ digit_6: '',
279
+ digit_7: '',
280
+ digit_8: '',
281
+ digit_9: '',
282
+ digit_0: '',
283
+ result_name: ''
284
+ };
285
+
286
+ const originalNode: Node = {
287
+ uuid: 'test-node',
288
+ actions: [],
289
+ router: {
290
+ type: 'switch',
291
+ categories: [
292
+ {
293
+ uuid: 'orig-sales',
294
+ name: 'Sales',
295
+ exit_uuid: 'orig-sales-exit'
296
+ },
297
+ {
298
+ uuid: 'orig-support',
299
+ name: 'Support',
300
+ exit_uuid: 'orig-support-exit'
301
+ },
302
+ {
303
+ uuid: 'orig-other',
304
+ name: 'Other',
305
+ exit_uuid: 'orig-other-exit'
306
+ }
307
+ ],
308
+ cases: [
309
+ {
310
+ uuid: 'orig-case-1',
311
+ type: 'has_number_eq',
312
+ arguments: ['1'],
313
+ category_uuid: 'orig-sales'
314
+ },
315
+ {
316
+ uuid: 'orig-case-2',
317
+ type: 'has_number_eq',
318
+ arguments: ['2'],
319
+ category_uuid: 'orig-support'
320
+ }
321
+ ]
322
+ },
323
+ exits: [
324
+ { uuid: 'orig-sales-exit', destination_uuid: 'dest-1' },
325
+ { uuid: 'orig-support-exit', destination_uuid: 'dest-2' },
326
+ { uuid: 'orig-other-exit', destination_uuid: null }
327
+ ]
328
+ };
329
+
330
+ const result = wait_for_menu.fromFormData!(formData, originalNode);
331
+
332
+ // Category UUIDs preserved
333
+ const sales = result.router!.categories.find((c) => c.name === 'Sales');
334
+ expect(sales?.uuid).to.equal('orig-sales');
335
+ expect(sales?.exit_uuid).to.equal('orig-sales-exit');
336
+
337
+ const other = result.router!.categories.find((c) => c.name === 'Other');
338
+ expect(other?.uuid).to.equal('orig-other');
339
+
340
+ // Case UUIDs preserved
341
+ const case1 = result.router!.cases.find(
342
+ (c: any) => c.arguments[0] === '1'
343
+ );
344
+ expect(case1?.uuid).to.equal('orig-case-1');
345
+
346
+ // Exit destinations preserved
347
+ const salesExit = result.exits.find((e) => e.uuid === 'orig-sales-exit');
348
+ expect(salesExit?.destination_uuid).to.equal('dest-1');
349
+ });
350
+
351
+ it('handles all empty digits', () => {
352
+ const formData = {
353
+ uuid: 'test-node',
354
+ digit_1: '',
355
+ digit_2: '',
356
+ digit_3: '',
357
+ digit_4: '',
358
+ digit_5: '',
359
+ digit_6: '',
360
+ digit_7: '',
361
+ digit_8: '',
362
+ digit_9: '',
363
+ digit_0: '',
364
+ result_name: ''
365
+ };
366
+
367
+ const originalNode: Node = {
368
+ uuid: 'test-node',
369
+ actions: [],
370
+ exits: [],
371
+ router: { type: 'switch', categories: [] }
372
+ };
373
+
374
+ const result = wait_for_menu.fromFormData!(formData, originalNode);
375
+
376
+ // Should still have Other category
377
+ expect(result.router?.categories).to.have.length(1);
378
+ expect(result.router!.categories[0].name).to.equal('Other');
379
+ expect(result.router?.cases).to.have.length(0);
380
+ expect(result.exits).to.have.length(1);
381
+ });
382
+ });
383
+ });
@@ -7,15 +7,13 @@ describe('temba-floating-tab', () => {
7
7
  const tab = (await getComponent('temba-floating-tab', {
8
8
  icon: 'phone',
9
9
  label: 'Phone Simulator',
10
- color: '#10b981',
11
- top: 100
10
+ color: '#10b981'
12
11
  })) as FloatingTab;
13
12
 
14
13
  assert.instanceOf(tab, FloatingTab);
15
14
  expect(tab.icon).to.equal('phone');
16
15
  expect(tab.label).to.equal('Phone Simulator');
17
16
  expect(tab.color).to.equal('#10b981');
18
- expect(tab.top).to.equal(100);
19
17
  expect(tab.hidden).to.equal(false);
20
18
 
21
19
  await assertScreenshot('floating-tab/default', getClip(tab));
@@ -75,21 +73,21 @@ describe('temba-floating-tab', () => {
75
73
  icon: 'phone',
76
74
  label: 'Phone',
77
75
  color: '#10b981',
78
- top: 100
76
+ order: 1
79
77
  })) as FloatingTab;
80
78
 
81
79
  const tab2 = (await getComponent('temba-floating-tab', {
82
80
  icon: 'globe',
83
81
  label: 'Translation',
84
82
  color: '#6b7280',
85
- top: 200
83
+ order: 2
86
84
  })) as FloatingTab;
87
85
 
88
86
  const tab3 = (await getComponent('temba-floating-tab', {
89
87
  icon: 'clock',
90
88
  label: 'History',
91
89
  color: '#8b5cf6',
92
- top: 300
90
+ order: 3
93
91
  })) as FloatingTab;
94
92
 
95
93
  await assertScreenshot('floating-tab/green', getClip(tab1));