@nyaruka/temba-components 0.131.1 → 0.131.2

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 (427) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +61 -1
  3. package/demo/data/flows/food-order.json +2 -2
  4. package/demo/data/flows/sample-flow.json +74 -125
  5. package/dist/static/svg/index.svg +1 -1
  6. package/dist/temba-components.js +1155 -618
  7. package/dist/temba-components.js.map +1 -1
  8. package/out-tsc/src/Icons.js +4 -1
  9. package/out-tsc/src/Icons.js.map +1 -1
  10. package/out-tsc/src/events.js.map +1 -1
  11. package/out-tsc/src/flow/CanvasMenu.js +200 -0
  12. package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
  13. package/out-tsc/src/flow/CanvasNode.js +327 -19
  14. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  15. package/out-tsc/src/flow/Editor.js +562 -66
  16. package/out-tsc/src/flow/Editor.js.map +1 -1
  17. package/out-tsc/src/flow/NodeEditor.js +240 -93
  18. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  19. package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
  20. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
  21. package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
  22. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
  24. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  25. package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
  26. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  27. package/out-tsc/src/flow/actions/play_audio.js +2 -2
  28. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  29. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
  30. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  31. package/out-tsc/src/flow/actions/request_optin.js +2 -2
  32. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  33. package/out-tsc/src/flow/actions/say_msg.js +2 -2
  34. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  35. package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
  36. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  37. package/out-tsc/src/flow/actions/send_email.js +4 -5
  38. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  39. package/out-tsc/src/flow/actions/send_msg.js +9 -19
  40. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  41. package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
  42. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  43. package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
  44. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  45. package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
  46. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  47. package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
  48. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  49. package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
  50. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  51. package/out-tsc/src/flow/actions/set_run_result.js +3 -3
  52. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  53. package/out-tsc/src/flow/actions/start_session.js +180 -6
  54. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  55. package/out-tsc/src/flow/config.js +11 -15
  56. package/out-tsc/src/flow/config.js.map +1 -1
  57. package/out-tsc/src/flow/currencies.js +45 -0
  58. package/out-tsc/src/flow/currencies.js.map +1 -0
  59. package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
  60. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
  61. package/out-tsc/src/flow/nodes/shared.js +17 -0
  62. package/out-tsc/src/flow/nodes/shared.js.map +1 -0
  63. package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
  64. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  65. package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
  66. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  67. package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
  68. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  69. package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
  70. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  71. package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
  72. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
  73. package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
  74. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  75. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
  76. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  77. package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
  78. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  79. package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
  80. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
  81. package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  83. package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  85. package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  87. package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
  88. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
  90. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
  92. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
  94. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  95. package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
  96. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
  97. package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
  98. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
  100. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  101. package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
  102. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  103. package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
  104. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
  105. package/out-tsc/src/flow/types.js +71 -12
  106. package/out-tsc/src/flow/types.js.map +1 -1
  107. package/out-tsc/src/flow/utils.js +101 -14
  108. package/out-tsc/src/flow/utils.js.map +1 -1
  109. package/out-tsc/src/form/FieldRenderer.js +2 -4
  110. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  111. package/out-tsc/src/interfaces.js +3 -0
  112. package/out-tsc/src/interfaces.js.map +1 -1
  113. package/out-tsc/src/list/SortableList.js +98 -33
  114. package/out-tsc/src/list/SortableList.js.map +1 -1
  115. package/out-tsc/src/live/ContactChat.js +15 -18
  116. package/out-tsc/src/live/ContactChat.js.map +1 -1
  117. package/out-tsc/src/store/AppState.js +53 -0
  118. package/out-tsc/src/store/AppState.js.map +1 -1
  119. package/out-tsc/src/utils.js +254 -13
  120. package/out-tsc/src/utils.js.map +1 -1
  121. package/out-tsc/temba-modules.js +4 -0
  122. package/out-tsc/temba-modules.js.map +1 -1
  123. package/out-tsc/test/ActionHelper.js +3 -3
  124. package/out-tsc/test/ActionHelper.js.map +1 -1
  125. package/out-tsc/test/NodeHelper.js +6 -3
  126. package/out-tsc/test/NodeHelper.js.map +1 -1
  127. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  128. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  129. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  130. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  131. package/out-tsc/test/actions/send_email.test.js +17 -23
  132. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  133. package/out-tsc/test/actions/send_msg.test.js +33 -15
  134. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  135. package/out-tsc/test/actions/start_session.test.js +116 -0
  136. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  137. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  138. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  139. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  140. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  141. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  142. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  143. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  144. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  145. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  146. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  147. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  148. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  149. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  150. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  151. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  152. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  153. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  154. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  155. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  156. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  157. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  158. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  159. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  160. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  161. package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
  162. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  163. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  164. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  165. package/out-tsc/test/temba-node-editor.test.js +3 -1
  166. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  167. package/out-tsc/test/temba-node-type-selector.test.js +115 -0
  168. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  169. package/out-tsc/test/temba-omnibox.test.js +2 -1
  170. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  171. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  172. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  173. package/out-tsc/test/temba-utils-index.test.js +1 -27
  174. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  175. package/out-tsc/test/utils.test.js +2 -0
  176. package/out-tsc/test/utils.test.js.map +1 -1
  177. package/package.json +2 -1
  178. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  179. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  180. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  181. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  182. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  183. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  184. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  185. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  186. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  187. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  188. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  189. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  190. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  191. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  192. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  193. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  194. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  195. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  196. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  197. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  198. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  199. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  200. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  201. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  202. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  203. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  204. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  205. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  206. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  207. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  208. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  209. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  210. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  211. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  212. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  213. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  214. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  215. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  216. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  217. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  218. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  219. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  220. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  221. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  222. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  223. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  224. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  225. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  226. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  227. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  228. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  229. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  230. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  231. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  232. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  233. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  234. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  235. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  236. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  237. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  238. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  239. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  240. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  241. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  242. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  243. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  244. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  245. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  246. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  247. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  248. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  249. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  250. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  251. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  252. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  253. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  254. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  255. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  256. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  257. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  258. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  259. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  260. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  261. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  262. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  263. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  264. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  265. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  266. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  267. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  268. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  269. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  270. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  271. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  272. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  273. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  274. package/screenshots/truth/canvas-menu/open.png +0 -0
  275. package/screenshots/truth/editor/router.png +0 -0
  276. package/screenshots/truth/editor/wait.png +0 -0
  277. package/screenshots/truth/list/fields-dragging.png +0 -0
  278. package/screenshots/truth/list/sortable-dragging.png +0 -0
  279. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  280. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  281. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  282. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  283. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  284. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  285. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  286. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  287. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  288. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  289. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  290. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  291. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  292. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  293. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  294. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  295. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  296. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  297. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  298. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  299. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  300. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  301. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  302. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  303. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  304. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  305. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  306. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  307. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  308. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  309. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  310. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  311. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  312. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  313. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  314. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  315. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  316. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  317. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  318. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  319. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  320. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  321. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  322. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  323. package/src/Icons.ts +4 -1
  324. package/src/events.ts +2 -6
  325. package/src/flow/CanvasMenu.ts +217 -0
  326. package/src/flow/CanvasNode.ts +408 -10
  327. package/src/flow/Editor.ts +683 -44
  328. package/src/flow/NodeEditor.ts +304 -125
  329. package/src/flow/NodeTypeSelector.ts +592 -0
  330. package/src/flow/actions/add_contact_groups.ts +4 -4
  331. package/src/flow/actions/add_contact_urn.ts +76 -4
  332. package/src/flow/actions/add_input_labels.ts +4 -4
  333. package/src/flow/actions/play_audio.ts +2 -2
  334. package/src/flow/actions/remove_contact_groups.ts +14 -6
  335. package/src/flow/actions/request_optin.ts +2 -2
  336. package/src/flow/actions/say_msg.ts +2 -2
  337. package/src/flow/actions/send_broadcast.ts +85 -23
  338. package/src/flow/actions/send_email.ts +10 -6
  339. package/src/flow/actions/send_msg.ts +22 -32
  340. package/src/flow/actions/set_contact_channel.ts +5 -11
  341. package/src/flow/actions/set_contact_field.ts +20 -25
  342. package/src/flow/actions/set_contact_language.ts +9 -4
  343. package/src/flow/actions/set_contact_name.ts +3 -15
  344. package/src/flow/actions/set_contact_status.ts +3 -3
  345. package/src/flow/actions/set_run_result.ts +4 -4
  346. package/src/flow/actions/start_session.ts +208 -6
  347. package/src/flow/config.ts +13 -15
  348. package/src/flow/currencies.ts +51 -0
  349. package/src/flow/nodes/shared-rules.ts +301 -0
  350. package/src/flow/nodes/shared.ts +18 -0
  351. package/src/flow/nodes/split_by_airtime.ts +238 -5
  352. package/src/flow/nodes/split_by_contact_field.ts +185 -3
  353. package/src/flow/nodes/split_by_expression.ts +94 -2
  354. package/src/flow/nodes/split_by_groups.ts +15 -10
  355. package/src/flow/nodes/split_by_intent.ts +7 -0
  356. package/src/flow/nodes/split_by_llm.ts +4 -3
  357. package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
  358. package/src/flow/nodes/split_by_random.ts +5 -5
  359. package/src/flow/nodes/split_by_resthook.ts +130 -0
  360. package/src/flow/nodes/split_by_run_result.ts +249 -3
  361. package/src/flow/nodes/split_by_scheme.ts +192 -2
  362. package/src/flow/nodes/split_by_subflow.ts +6 -4
  363. package/src/flow/nodes/split_by_ticket.ts +4 -3
  364. package/src/flow/nodes/split_by_webhook.ts +6 -5
  365. package/src/flow/nodes/wait_for_audio.ts +2 -2
  366. package/src/flow/nodes/wait_for_digits.ts +2 -2
  367. package/src/flow/nodes/wait_for_image.ts +2 -2
  368. package/src/flow/nodes/wait_for_location.ts +2 -2
  369. package/src/flow/nodes/wait_for_menu.ts +2 -2
  370. package/src/flow/nodes/wait_for_response.ts +48 -679
  371. package/src/flow/nodes/wait_for_video.ts +2 -2
  372. package/src/flow/types.ts +109 -23
  373. package/src/flow/utils.ts +108 -14
  374. package/src/form/FieldRenderer.ts +2 -4
  375. package/src/interfaces.ts +3 -0
  376. package/src/list/SortableList.ts +109 -34
  377. package/src/live/ContactChat.ts +15 -18
  378. package/src/store/AppState.ts +69 -0
  379. package/src/store/flow-definition.d.ts +2 -5
  380. package/src/utils.ts +332 -12
  381. package/static/api/channels.json +46 -0
  382. package/static/api/resthooks.json +31 -0
  383. package/static/svg/index.svg +1 -1
  384. package/static/svg/work/traced/lightning-02.svg +1 -0
  385. package/static/svg/work/used/lightning-02.svg +3 -0
  386. package/temba-modules.ts +4 -0
  387. package/test/ActionHelper.ts +3 -3
  388. package/test/NodeHelper.ts +6 -3
  389. package/test/actions/add_contact_urn.test.ts +287 -0
  390. package/test/actions/send_broadcast.test.ts +190 -0
  391. package/test/actions/send_email.test.ts +17 -23
  392. package/test/actions/send_msg.test.ts +39 -15
  393. package/test/actions/start_session.test.ts +151 -0
  394. package/test/nodes/split_by_airtime.test.ts +673 -0
  395. package/test/nodes/split_by_contact_field.test.ts +451 -0
  396. package/test/nodes/split_by_expression.test.ts +751 -0
  397. package/test/nodes/split_by_random.test.ts +3 -3
  398. package/test/nodes/split_by_resthook.test.ts +398 -0
  399. package/test/nodes/split_by_run_result.test.ts +1109 -0
  400. package/test/nodes/split_by_scheme.test.ts +486 -0
  401. package/test/nodes/split_by_subflow.test.ts +381 -0
  402. package/test/nodes/wait_for_digits.test.ts +2 -2
  403. package/test/nodes/wait_for_response.test.ts +2 -1
  404. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  405. package/test/temba-canvas-menu.test.ts +156 -0
  406. package/test/temba-flow-editor-node.test.ts +102 -2
  407. package/test/temba-flow-editor.test.ts +7 -8
  408. package/test/temba-node-editor.test.ts +3 -1
  409. package/test/temba-node-type-selector.test.ts +152 -0
  410. package/test/temba-omnibox.test.ts +2 -1
  411. package/test/temba-sortable-list.test.ts +69 -0
  412. package/test/temba-utils-index.test.ts +0 -35
  413. package/test/utils.test.ts +2 -0
  414. package/test-assets/contacts/history.json +14 -20
  415. package/web-dev-server.config.mjs +3 -1
  416. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  417. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  418. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  419. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  420. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  421. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  422. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  423. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  424. package/src/flow/actions/call_classifier.ts +0 -12
  425. package/src/flow/actions/call_resthook.ts +0 -12
  426. package/src/flow/actions/split_by_expression_example.ts +0 -88
  427. package/src/flow/actions/transfer_airtime.ts +0 -12
@@ -0,0 +1 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.740 1.118 C 7.221 1.327,6.920 1.836,4.718 6.240 C 2.218 11.241,2.170 11.364,2.475 12.093 C 2.609 12.414,2.878 12.708,3.123 12.801 C 3.353 12.888,4.796 12.960,6.330 12.960 C 7.865 12.960,9.120 12.999,9.120 13.046 C 9.120 13.093,8.544 15.048,7.840 17.390 C 7.136 19.731,6.560 21.851,6.560 22.100 C 6.560 22.690,7.179 23.129,7.738 22.935 C 8.123 22.802,20.460 10.117,20.999 9.302 C 21.442 8.632,21.362 7.938,20.788 7.454 L 20.295 7.040 16.868 7.040 C 14.982 7.040,13.440 7.022,13.441 7.000 C 13.441 6.978,13.837 5.916,14.320 4.640 C 14.803 3.364,15.199 2.158,15.199 1.960 C 15.200 1.762,15.056 1.456,14.880 1.280 C 14.580 0.980,14.354 0.961,11.320 0.970 C 9.538 0.976,7.927 1.042,7.740 1.118 M12.622 3.480 C 12.528 3.722,12.134 4.769,11.746 5.808 C 10.545 9.020,10.460 8.939,15.116 8.997 L 18.613 9.040 14.245 13.565 C 11.843 16.054,9.851 18.064,9.819 18.032 C 9.786 18.000,10.156 16.653,10.640 15.040 C 11.124 13.426,11.520 12.010,11.520 11.893 C 11.520 11.776,11.376 11.536,11.200 11.360 C 10.902 11.062,10.667 11.040,7.759 11.040 L 4.639 11.040 6.640 7.040 L 8.641 3.040 10.717 3.040 L 12.792 3.040 12.622 3.480 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M14.2495 2H8.49395C8.31447 2 8.22473 2 8.14551 2.02733C8.07544 2.05149 8.01163 2.09093 7.95868 2.14279C7.89881 2.20143 7.85868 2.2817 7.77841 2.44223L3.57841 10.8422C3.38673 11.2256 3.29089 11.4173 3.31391 11.5731C3.33401 11.7091 3.40927 11.8309 3.52197 11.9097C3.65104 12 3.86534 12 4.29395 12H10.4995L7.49953 22L19.6926 9.35531C20.104 8.9287 20.3097 8.7154 20.3217 8.53288C20.3321 8.37446 20.2667 8.22049 20.1454 8.11803C20.0057 8 19.7094 8 19.1167 8H11.9995L14.2495 2Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
package/temba-modules.ts CHANGED
@@ -58,6 +58,8 @@ import { MediaPicker } from './src/form/MediaPicker';
58
58
  import { Editor } from './src/flow/Editor';
59
59
  import { CanvasNode } from './src/flow/CanvasNode';
60
60
  import { StickyNote } from './src/flow/StickyNote';
61
+ import { CanvasMenu } from './src/flow/CanvasMenu';
62
+ import { NodeTypeSelector } from './src/flow/NodeTypeSelector';
61
63
  import { ContactNotepad } from './src/live/ContactNotepad';
62
64
  import { ProgressBar } from './src/display/ProgressBar';
63
65
  import { StartProgress } from './src/live/StartProgress';
@@ -143,6 +145,8 @@ addCustomElement('temba-flow-editor', Editor);
143
145
  addCustomElement('temba-node-editor', NodeEditor);
144
146
  addCustomElement('temba-flow-node', CanvasNode);
145
147
  addCustomElement('temba-sticky-note', StickyNote);
148
+ addCustomElement('temba-canvas-menu', CanvasMenu);
149
+ addCustomElement('temba-node-type-selector', NodeTypeSelector);
146
150
  addCustomElement('temba-contact-notepad', ContactNotepad);
147
151
  addCustomElement('temba-progress', ProgressBar);
148
152
  addCustomElement('temba-start-progress', StartProgress);
@@ -111,9 +111,9 @@ export class ActionTest<T extends Action> {
111
111
  expect(convertedAction.uuid).to.equal(action.uuid);
112
112
  expect(convertedAction.type).to.equal(action.type);
113
113
 
114
- // Validate the converted action
114
+ // Validate the form data
115
115
  if (this.actionConfig.validate) {
116
- const validation = this.actionConfig.validate(convertedAction);
116
+ const validation = this.actionConfig.validate(formData);
117
117
  expect(validation.valid).to.be.true;
118
118
  }
119
119
  }
@@ -126,7 +126,7 @@ export class ActionTest<T extends Action> {
126
126
  testBasicProperties() {
127
127
  it('has correct basic properties', () => {
128
128
  expect(this.actionConfig.name).to.be.a('string');
129
- expect(this.actionConfig.color).to.exist;
129
+ expect(this.actionConfig.group).to.exist;
130
130
 
131
131
  // toFormData and fromFormData are optional - only needed for complex data transformations
132
132
  if (this.actionConfig.toFormData) {
@@ -144,9 +144,12 @@ export class NodeTest<T extends Node> {
144
144
  expect(this.nodeConfig.name).to.be.a('string');
145
145
  }
146
146
 
147
- // Color is optional
148
- if (this.nodeConfig.color) {
149
- expect(this.nodeConfig.color).to.be.a('string');
147
+ // EditorType is optional but recommended
148
+ if (this.nodeConfig.editorType) {
149
+ expect(this.nodeConfig.editorType).to.be.an('object');
150
+ expect(this.nodeConfig.editorType.color).to.be.a('string');
151
+ expect(this.nodeConfig.editorType.title).to.be.a('string');
152
+ expect(this.nodeConfig.editorType.description).to.be.a('string');
150
153
  }
151
154
 
152
155
  // toFormData and fromFormData are optional - only needed for complex data transformations
@@ -0,0 +1,287 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { add_contact_urn } from '../../src/flow/actions/add_contact_urn';
3
+ import { AddContactUrn } from '../../src/store/flow-definition';
4
+ import { ActionTest } from '../ActionHelper';
5
+
6
+ /**
7
+ * Test suite for the add_contact_urn action configuration.
8
+ */
9
+ describe('add_contact_urn action config', () => {
10
+ const helper = new ActionTest(add_contact_urn, 'add_contact_urn');
11
+
12
+ describe('basic properties', () => {
13
+ helper.testBasicProperties();
14
+
15
+ it('has correct name', () => {
16
+ expect(add_contact_urn.name).to.equal('Add URN');
17
+ });
18
+ });
19
+
20
+ describe('action scenarios', () => {
21
+ helper.testAction(
22
+ {
23
+ uuid: 'test-action-1',
24
+ type: 'add_contact_urn',
25
+ scheme: 'tel',
26
+ path: '+12065551212'
27
+ } as AddContactUrn,
28
+ 'phone-number'
29
+ );
30
+
31
+ helper.testAction(
32
+ {
33
+ uuid: 'test-action-2',
34
+ type: 'add_contact_urn',
35
+ scheme: 'whatsapp',
36
+ path: '+447123456789'
37
+ } as AddContactUrn,
38
+ 'whatsapp'
39
+ );
40
+
41
+ helper.testAction(
42
+ {
43
+ uuid: 'test-action-3',
44
+ type: 'add_contact_urn',
45
+ scheme: 'facebook',
46
+ path: '1234567890'
47
+ } as AddContactUrn,
48
+ 'facebook-id'
49
+ );
50
+
51
+ helper.testAction(
52
+ {
53
+ uuid: 'test-action-4',
54
+ type: 'add_contact_urn',
55
+ scheme: 'telegram',
56
+ path: '987654321'
57
+ } as AddContactUrn,
58
+ 'telegram-id'
59
+ );
60
+
61
+ helper.testAction(
62
+ {
63
+ uuid: 'test-action-5',
64
+ type: 'add_contact_urn',
65
+ scheme: 'instagram',
66
+ path: 'user_instagram_handle'
67
+ } as AddContactUrn,
68
+ 'instagram-handle'
69
+ );
70
+
71
+ helper.testAction(
72
+ {
73
+ uuid: 'test-action-6',
74
+ type: 'add_contact_urn',
75
+ scheme: 'tel',
76
+ path: '@run.results.phone_number'
77
+ } as AddContactUrn,
78
+ 'expression-phone'
79
+ );
80
+
81
+ helper.testAction(
82
+ {
83
+ uuid: 'test-action-7',
84
+ type: 'add_contact_urn',
85
+ scheme: 'facebook',
86
+ path: '@fields.facebook_id'
87
+ } as AddContactUrn,
88
+ 'expression-facebook'
89
+ );
90
+
91
+ helper.testAction(
92
+ {
93
+ uuid: 'test-action-8',
94
+ type: 'add_contact_urn',
95
+ scheme: 'line',
96
+ path: 'U1234567890abcdef1234567890abcdef'
97
+ } as AddContactUrn,
98
+ 'line-id'
99
+ );
100
+
101
+ helper.testAction(
102
+ {
103
+ uuid: 'test-action-9',
104
+ type: 'add_contact_urn',
105
+ scheme: 'viber',
106
+ path: '0123456789ABC='
107
+ } as AddContactUrn,
108
+ 'viber-id'
109
+ );
110
+
111
+ helper.testAction(
112
+ {
113
+ uuid: 'test-action-10',
114
+ type: 'add_contact_urn',
115
+ scheme: 'wechat',
116
+ path: 'wechatuser123'
117
+ } as AddContactUrn,
118
+ 'wechat-id'
119
+ );
120
+ });
121
+
122
+ describe('validation', () => {
123
+ it('validates that scheme is required', () => {
124
+ const formData = {
125
+ scheme: [],
126
+ path: '+12065551212'
127
+ };
128
+
129
+ const result = add_contact_urn.validate!(formData);
130
+
131
+ expect(result.valid).to.be.false;
132
+ expect(result.errors.scheme).to.equal('URN type is required');
133
+ });
134
+
135
+ it('validates that path is required', () => {
136
+ const formData = {
137
+ scheme: [{ name: 'Phone Number', value: 'tel' }],
138
+ path: ''
139
+ };
140
+
141
+ const result = add_contact_urn.validate!(formData);
142
+
143
+ expect(result.valid).to.be.false;
144
+ expect(result.errors.path).to.equal('URN value is required');
145
+ });
146
+
147
+ it('validates that path with only whitespace is invalid', () => {
148
+ const formData = {
149
+ scheme: [{ name: 'Phone Number', value: 'tel' }],
150
+ path: ' '
151
+ };
152
+
153
+ const result = add_contact_urn.validate!(formData);
154
+
155
+ expect(result.valid).to.be.false;
156
+ expect(result.errors.path).to.equal('URN value is required');
157
+ });
158
+
159
+ it('validates successfully with both scheme and path', () => {
160
+ const formData = {
161
+ scheme: [{ name: 'Phone Number', value: 'tel' }],
162
+ path: '+12065551212'
163
+ };
164
+
165
+ const result = add_contact_urn.validate!(formData);
166
+
167
+ expect(result.valid).to.be.true;
168
+ expect(Object.keys(result.errors)).to.have.lengthOf(0);
169
+ });
170
+
171
+ it('validates successfully with expression in path', () => {
172
+ const formData = {
173
+ scheme: [{ name: 'Facebook ID', value: 'facebook' }],
174
+ path: '@fields.facebook_id'
175
+ };
176
+
177
+ const result = add_contact_urn.validate!(formData);
178
+
179
+ expect(result.valid).to.be.true;
180
+ expect(Object.keys(result.errors)).to.have.lengthOf(0);
181
+ });
182
+ });
183
+
184
+ describe('round-trip transformation', () => {
185
+ it('should preserve action structure through toFormData -> fromFormData', () => {
186
+ const originalAction: AddContactUrn = {
187
+ uuid: 'test-uuid-123',
188
+ type: 'add_contact_urn',
189
+ scheme: 'whatsapp',
190
+ path: '+447123456789'
191
+ };
192
+
193
+ const formData = add_contact_urn.toFormData!(originalAction);
194
+ const convertedAction = add_contact_urn.fromFormData!(
195
+ formData
196
+ ) as AddContactUrn;
197
+
198
+ expect(convertedAction.uuid).to.equal(originalAction.uuid);
199
+ expect(convertedAction.type).to.equal(originalAction.type);
200
+ expect(convertedAction.scheme).to.equal(originalAction.scheme);
201
+ expect(convertedAction.path).to.equal(originalAction.path);
202
+ });
203
+
204
+ it('should handle different URN schemes correctly', () => {
205
+ const schemes = ['tel', 'facebook', 'telegram', 'instagram', 'whatsapp'];
206
+
207
+ schemes.forEach((scheme) => {
208
+ const originalAction: AddContactUrn = {
209
+ uuid: 'test-uuid',
210
+ type: 'add_contact_urn',
211
+ scheme: scheme,
212
+ path: 'test-value'
213
+ };
214
+
215
+ const formData = add_contact_urn.toFormData!(originalAction);
216
+ const convertedAction = add_contact_urn.fromFormData!(
217
+ formData
218
+ ) as AddContactUrn;
219
+
220
+ expect(convertedAction.scheme).to.equal(scheme);
221
+ });
222
+ });
223
+
224
+ it('should handle expressions in path', () => {
225
+ const originalAction: AddContactUrn = {
226
+ uuid: 'test-uuid',
227
+ type: 'add_contact_urn',
228
+ scheme: 'tel',
229
+ path: '@run.results.phone_number'
230
+ };
231
+
232
+ const formData = add_contact_urn.toFormData!(originalAction);
233
+ const convertedAction = add_contact_urn.fromFormData!(
234
+ formData
235
+ ) as AddContactUrn;
236
+
237
+ expect(convertedAction.path).to.equal('@run.results.phone_number');
238
+ });
239
+
240
+ it('should default to tel scheme if scheme is missing in form data', () => {
241
+ const formData = {
242
+ uuid: 'test-uuid',
243
+ scheme: [],
244
+ path: '+12065551212'
245
+ };
246
+
247
+ const convertedAction = add_contact_urn.fromFormData!(
248
+ formData
249
+ ) as AddContactUrn;
250
+
251
+ expect(convertedAction.scheme).to.equal('tel');
252
+ });
253
+ });
254
+
255
+ describe('form data structure', () => {
256
+ it('should convert scheme to select array format in toFormData', () => {
257
+ const action: AddContactUrn = {
258
+ uuid: 'test-uuid',
259
+ type: 'add_contact_urn',
260
+ scheme: 'whatsapp',
261
+ path: '+447123456789'
262
+ };
263
+
264
+ const formData = add_contact_urn.toFormData!(action);
265
+
266
+ expect(formData.scheme).to.be.an('array');
267
+ expect(formData.scheme).to.have.lengthOf(1);
268
+ expect(formData.scheme[0]).to.deep.include({
269
+ name: 'WhatsApp Number',
270
+ value: 'whatsapp'
271
+ });
272
+ });
273
+
274
+ it('should handle unknown scheme gracefully', () => {
275
+ const action: AddContactUrn = {
276
+ uuid: 'test-uuid',
277
+ type: 'add_contact_urn',
278
+ scheme: 'unknown_scheme',
279
+ path: 'some-value'
280
+ };
281
+
282
+ const formData = add_contact_urn.toFormData!(action);
283
+
284
+ expect(formData.scheme).to.be.null;
285
+ });
286
+ });
287
+ });
@@ -0,0 +1,190 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { send_broadcast } from '../../src/flow/actions/send_broadcast';
3
+ import { SendBroadcast } from '../../src/store/flow-definition';
4
+ import { ActionTest } from '../ActionHelper';
5
+
6
+ /**
7
+ * Test suite for the send_broadcast action configuration.
8
+ */
9
+ describe('send_broadcast action config', () => {
10
+ const helper = new ActionTest(send_broadcast, 'send_broadcast');
11
+
12
+ describe('basic properties', () => {
13
+ helper.testBasicProperties();
14
+
15
+ it('has correct name', () => {
16
+ expect(send_broadcast.name).to.equal('Send Broadcast');
17
+ });
18
+ });
19
+
20
+ describe('action scenarios', () => {
21
+ helper.testAction(
22
+ {
23
+ uuid: 'test-action-1',
24
+ type: 'send_broadcast',
25
+ text: 'Important announcement for all subscribers!',
26
+ groups: [
27
+ { uuid: 'group-1', name: 'Subscribers' },
28
+ { uuid: 'group-2', name: 'VIP Members' }
29
+ ],
30
+ contacts: []
31
+ } as SendBroadcast,
32
+ 'groups-only'
33
+ );
34
+
35
+ helper.testAction(
36
+ {
37
+ uuid: 'test-action-2',
38
+ type: 'send_broadcast',
39
+ text: 'Personal message to specific contacts.',
40
+ groups: [],
41
+ contacts: [
42
+ { uuid: 'contact-1', name: 'Alice Johnson' },
43
+ { uuid: 'contact-2', name: 'Bob Smith' }
44
+ ]
45
+ } as SendBroadcast,
46
+ 'contacts-only'
47
+ );
48
+
49
+ helper.testAction(
50
+ {
51
+ uuid: 'test-action-3',
52
+ type: 'send_broadcast',
53
+ text: 'Urgent update for everyone!',
54
+ groups: [{ uuid: 'group-1', name: 'All Members' }],
55
+ contacts: [{ uuid: 'contact-1', name: 'Admin' }]
56
+ } as SendBroadcast,
57
+ 'groups-and-contacts'
58
+ );
59
+
60
+ helper.testAction(
61
+ {
62
+ uuid: 'test-action-4',
63
+ type: 'send_broadcast',
64
+ text: 'Multi-line broadcast message.\n\nThis is the second paragraph.\n\nAnd the third.',
65
+ groups: [{ uuid: 'group-1', name: 'Newsletter Subscribers' }],
66
+ contacts: []
67
+ } as SendBroadcast,
68
+ 'multiline-text'
69
+ );
70
+
71
+ helper.testAction(
72
+ {
73
+ uuid: 'test-action-5',
74
+ type: 'send_broadcast',
75
+ text: 'Broadcast with attachments',
76
+ groups: [{ uuid: 'group-1', name: 'Members' }],
77
+ contacts: [],
78
+ attachments: [
79
+ 'image/jpeg:https://example.com/photo.jpg',
80
+ 'application/pdf:https://example.com/document.pdf'
81
+ ]
82
+ } as SendBroadcast,
83
+ 'with-attachments'
84
+ );
85
+
86
+ helper.testAction(
87
+ {
88
+ uuid: 'test-action-6',
89
+ type: 'send_broadcast',
90
+ text: 'Message to many groups',
91
+ groups: [
92
+ { uuid: 'group-1', name: 'Group A' },
93
+ { uuid: 'group-2', name: 'Group B' },
94
+ { uuid: 'group-3', name: 'Group C' },
95
+ { uuid: 'group-4', name: 'Group D' }
96
+ ],
97
+ contacts: []
98
+ } as SendBroadcast,
99
+ 'many-groups'
100
+ );
101
+ });
102
+
103
+ describe('form data conversion', () => {
104
+ it('converts action to form data correctly', () => {
105
+ const action: SendBroadcast = {
106
+ uuid: 'test-uuid',
107
+ type: 'send_broadcast',
108
+ text: 'Test message',
109
+ groups: [{ uuid: 'group-1', name: 'Test Group' }],
110
+ contacts: [{ uuid: 'contact-1', name: 'Test Contact' }],
111
+ attachments: ['image/jpeg:test.jpg']
112
+ };
113
+
114
+ const formData = send_broadcast.toFormData(action);
115
+
116
+ expect(formData.uuid).to.equal('test-uuid');
117
+ expect(formData.text).to.equal('Test message');
118
+ expect(formData.recipients).to.have.lengthOf(2);
119
+ expect(formData.attachments).to.deep.equal(['image/jpeg:test.jpg']);
120
+ });
121
+
122
+ it('converts form data to action correctly with contacts only', () => {
123
+ const formData = {
124
+ uuid: 'test-uuid',
125
+ recipients: [
126
+ { uuid: 'contact-1', name: 'Contact 1' },
127
+ { uuid: 'contact-2', name: 'Contact 2' }
128
+ ],
129
+ text: 'Test message',
130
+ attachments: []
131
+ };
132
+
133
+ const action = send_broadcast.fromFormData(formData) as SendBroadcast;
134
+
135
+ expect(action.uuid).to.equal('test-uuid');
136
+ expect(action.type).to.equal('send_broadcast');
137
+ expect(action.text).to.equal('Test message');
138
+ expect(action.contacts).to.have.lengthOf(2);
139
+ expect(action.groups).to.have.lengthOf(0);
140
+ expect(action.attachments).to.be.undefined;
141
+ });
142
+
143
+ it('converts form data to action correctly with groups only', () => {
144
+ const formData = {
145
+ uuid: 'test-uuid',
146
+ recipients: [
147
+ { uuid: 'group-1', name: 'Group 1', group: true },
148
+ { uuid: 'group-2', name: 'Group 2', group: true }
149
+ ],
150
+ text: 'Test message',
151
+ attachments: []
152
+ };
153
+
154
+ const action = send_broadcast.fromFormData(formData) as SendBroadcast;
155
+
156
+ expect(action.contacts).to.have.lengthOf(0);
157
+ expect(action.groups).to.have.lengthOf(2);
158
+ });
159
+
160
+ it('converts form data to action correctly with mixed recipients', () => {
161
+ const formData = {
162
+ uuid: 'test-uuid',
163
+ recipients: [
164
+ { uuid: 'contact-1', name: 'Contact 1' },
165
+ { uuid: 'group-1', name: 'Group 1', group: true }
166
+ ],
167
+ text: 'Test message',
168
+ attachments: ['image/jpeg:test.jpg']
169
+ };
170
+
171
+ const action = send_broadcast.fromFormData(formData) as SendBroadcast;
172
+
173
+ expect(action.contacts).to.have.lengthOf(1);
174
+ expect(action.groups).to.have.lengthOf(1);
175
+ expect(action.attachments).to.deep.equal(['image/jpeg:test.jpg']);
176
+ });
177
+
178
+ it('sanitizes text by trimming whitespace', () => {
179
+ const formData = {
180
+ uuid: 'test-uuid',
181
+ recipients: [{ uuid: 'contact-1', name: 'Test Contact' }],
182
+ text: ' Test message \n '
183
+ };
184
+
185
+ send_broadcast.sanitize(formData);
186
+
187
+ expect(formData.text).to.equal('Test message');
188
+ });
189
+ });
190
+ });
@@ -118,15 +118,14 @@ describe('send_email action config', () => {
118
118
 
119
119
  describe('validation edge cases', () => {
120
120
  it('fails validation for empty addresses array', () => {
121
- const action: SendEmail = {
121
+ const formData = {
122
122
  uuid: 'test-action',
123
- type: 'send_email',
124
123
  addresses: [],
125
124
  subject: 'Test Subject',
126
125
  body: 'Test Body'
127
126
  };
128
127
 
129
- const result = send_email.validate(action);
128
+ const result = send_email.validate(formData);
130
129
  expect(result.valid).to.be.false;
131
130
  expect(result.errors.addresses).to.equal(
132
131
  'At least one recipient email address is required'
@@ -134,15 +133,14 @@ describe('send_email action config', () => {
134
133
  });
135
134
 
136
135
  it('fails validation for null addresses', () => {
137
- const action: SendEmail = {
136
+ const formData = {
138
137
  uuid: 'test-action',
139
- type: 'send_email',
140
138
  addresses: null as any,
141
139
  subject: 'Test Subject',
142
140
  body: 'Test Body'
143
141
  };
144
142
 
145
- const result = send_email.validate(action);
143
+ const result = send_email.validate(formData);
146
144
  expect(result.valid).to.be.false;
147
145
  expect(result.errors.addresses).to.equal(
148
146
  'At least one recipient email address is required'
@@ -150,15 +148,14 @@ describe('send_email action config', () => {
150
148
  });
151
149
 
152
150
  it('fails validation for undefined addresses', () => {
153
- const action: SendEmail = {
151
+ const formData = {
154
152
  uuid: 'test-action',
155
- type: 'send_email',
156
153
  addresses: undefined as any,
157
154
  subject: 'Test Subject',
158
155
  body: 'Test Body'
159
156
  };
160
157
 
161
- const result = send_email.validate(action);
158
+ const result = send_email.validate(formData);
162
159
  expect(result.valid).to.be.false;
163
160
  expect(result.errors.addresses).to.equal(
164
161
  'At least one recipient email address is required'
@@ -166,47 +163,44 @@ describe('send_email action config', () => {
166
163
  });
167
164
 
168
165
  it('passes validation with valid email addresses', () => {
169
- const action: SendEmail = {
166
+ const formData = {
170
167
  uuid: 'test-action',
171
- type: 'send_email',
172
- addresses: ['valid@example.com'],
168
+ addresses: [{ name: 'valid@example.com', value: 'valid@example.com' }],
173
169
  subject: 'Test Subject',
174
170
  body: 'Test Body'
175
171
  };
176
172
 
177
- const result = send_email.validate(action);
173
+ const result = send_email.validate(formData);
178
174
  expect(result.valid).to.be.true;
179
175
  expect(Object.keys(result.errors)).to.have.length(0);
180
176
  });
181
177
 
182
178
  it('passes validation with multiple email addresses', () => {
183
- const action: SendEmail = {
179
+ const formData = {
184
180
  uuid: 'test-action',
185
- type: 'send_email',
186
181
  addresses: [
187
- 'user1@example.com',
188
- 'user2@example.com',
189
- 'user3@example.com'
182
+ { name: 'user1@example.com', value: 'user1@example.com' },
183
+ { name: 'user2@example.com', value: 'user2@example.com' },
184
+ { name: 'user3@example.com', value: 'user3@example.com' }
190
185
  ],
191
186
  subject: 'Test Subject',
192
187
  body: 'Test Body'
193
188
  };
194
189
 
195
- const result = send_email.validate(action);
190
+ const result = send_email.validate(formData);
196
191
  expect(result.valid).to.be.true;
197
192
  expect(Object.keys(result.errors)).to.have.length(0);
198
193
  });
199
194
 
200
195
  it('passes validation with empty subject and body', () => {
201
- const action: SendEmail = {
196
+ const formData = {
202
197
  uuid: 'test-action',
203
- type: 'send_email',
204
- addresses: ['test@example.com'],
198
+ addresses: [{ name: 'test@example.com', value: 'test@example.com' }],
205
199
  subject: '',
206
200
  body: ''
207
201
  };
208
202
 
209
- const result = send_email.validate(action);
203
+ const result = send_email.validate(formData);
210
204
  expect(result.valid).to.be.true;
211
205
  expect(Object.keys(result.errors)).to.have.length(0);
212
206
  });