@nyaruka/temba-components 0.129.3 → 0.129.4

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 (304) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/build.yml +135 -3
  3. package/CHANGELOG.md +18 -0
  4. package/demo/data/flows/sample-flow.json +110 -87
  5. package/demo/field-config-demo.html +135 -0
  6. package/dist/temba-components.js +1257 -675
  7. package/dist/temba-components.js.map +1 -1
  8. package/docs/ActionEditor-Migration.md +118 -0
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -0
  12. package/out-tsc/src/flow/Editor.js +107 -3
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +1200 -0
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -0
  16. package/out-tsc/src/flow/Plumber.js +0 -6
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
  19. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
  20. package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
  21. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
  22. package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
  23. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
  24. package/out-tsc/src/flow/actions/call_classifier.js +11 -0
  25. package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
  26. package/out-tsc/src/flow/actions/call_llm.js +11 -0
  27. package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
  28. package/out-tsc/src/flow/actions/call_resthook.js +11 -0
  29. package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
  30. package/out-tsc/src/flow/actions/call_webhook.js +122 -0
  31. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
  32. package/out-tsc/src/flow/actions/enter_flow.js +14 -0
  33. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  34. package/out-tsc/src/flow/actions/open_ticket.js +11 -0
  35. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
  36. package/out-tsc/src/flow/actions/play_audio.js +11 -0
  37. package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
  38. package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
  39. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
  40. package/out-tsc/src/flow/actions/request_optin.js +11 -0
  41. package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
  42. package/out-tsc/src/flow/actions/say_msg.js +11 -0
  43. package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
  44. package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
  45. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
  46. package/out-tsc/src/flow/actions/send_email.js +56 -0
  47. package/out-tsc/src/flow/actions/send_email.js.map +1 -0
  48. package/out-tsc/src/flow/actions/send_msg.js +55 -0
  49. package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
  52. package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
  53. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
  54. package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
  55. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
  56. package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
  57. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
  58. package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
  59. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
  60. package/out-tsc/src/flow/actions/set_run_result.js +10 -0
  61. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
  62. package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
  63. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
  64. package/out-tsc/src/flow/actions/start_session.js +11 -0
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -0
  66. package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
  67. package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
  68. package/out-tsc/src/flow/config.js +88 -193
  69. package/out-tsc/src/flow/config.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
  71. package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
  73. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
  75. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
  77. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
  78. package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
  79. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
  80. package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
  81. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
  83. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
  85. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
  87. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
  88. package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
  90. package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  92. package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
  94. package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
  95. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
  96. package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
  97. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
  98. package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
  100. package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
  101. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
  102. package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
  103. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
  104. package/out-tsc/src/flow/types.js +79 -0
  105. package/out-tsc/src/flow/types.js.map +1 -0
  106. package/out-tsc/src/flow/utils.js +65 -0
  107. package/out-tsc/src/flow/utils.js.map +1 -0
  108. package/out-tsc/src/form/ArrayEditor.js +199 -0
  109. package/out-tsc/src/form/ArrayEditor.js.map +1 -0
  110. package/out-tsc/src/form/BaseListEditor.js +128 -0
  111. package/out-tsc/src/form/BaseListEditor.js.map +1 -0
  112. package/out-tsc/src/form/Checkbox.js +17 -2
  113. package/out-tsc/src/form/Checkbox.js.map +1 -1
  114. package/out-tsc/src/form/Completion.js +6 -0
  115. package/out-tsc/src/form/Completion.js.map +1 -1
  116. package/out-tsc/src/form/FormField.js +110 -11
  117. package/out-tsc/src/form/FormField.js.map +1 -1
  118. package/out-tsc/src/form/KeyValueEditor.js +223 -0
  119. package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
  120. package/out-tsc/src/form/select/Select.js +77 -32
  121. package/out-tsc/src/form/select/Select.js.map +1 -1
  122. package/out-tsc/src/interfaces.js +6 -0
  123. package/out-tsc/src/interfaces.js.map +1 -1
  124. package/out-tsc/src/live/ContactChat.js +2 -76
  125. package/out-tsc/src/live/ContactChat.js.map +1 -1
  126. package/out-tsc/temba-modules.js +9 -2
  127. package/out-tsc/temba-modules.js.map +1 -1
  128. package/out-tsc/test/ActionHelper.js +116 -0
  129. package/out-tsc/test/ActionHelper.js.map +1 -0
  130. package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
  131. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
  132. package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
  133. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
  134. package/out-tsc/test/actions/send_email.test.js +160 -0
  135. package/out-tsc/test/actions/send_email.test.js.map +1 -0
  136. package/out-tsc/test/actions/send_msg.test.js +95 -0
  137. package/out-tsc/test/actions/send_msg.test.js.map +1 -0
  138. package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
  139. package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
  140. package/out-tsc/test/temba-checkbox.test.js +1 -1
  141. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  142. package/out-tsc/test/temba-field-config.test.js +133 -0
  143. package/out-tsc/test/temba-field-config.test.js.map +1 -0
  144. package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
  145. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  146. package/out-tsc/test/temba-node-editor.test.js +283 -0
  147. package/out-tsc/test/temba-node-editor.test.js.map +1 -0
  148. package/out-tsc/test/temba-select.test.js +85 -0
  149. package/out-tsc/test/temba-select.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  152. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  153. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  154. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  155. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  156. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  157. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  158. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  159. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  160. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  161. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  162. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  163. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  164. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  165. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  166. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  167. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  168. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  170. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  171. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  172. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  174. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  175. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  176. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  177. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  178. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  179. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  180. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  181. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  182. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  183. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  184. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  185. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  186. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  187. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  188. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  189. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  190. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  191. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  192. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  193. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  194. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  195. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  196. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  197. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  198. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  199. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  200. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  201. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  202. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  203. package/screenshots/truth/editor/router.png +0 -0
  204. package/screenshots/truth/editor/send_msg.png +0 -0
  205. package/screenshots/truth/editor/set_contact_language.png +0 -0
  206. package/screenshots/truth/editor/set_contact_name.png +0 -0
  207. package/screenshots/truth/editor/set_run_result.png +0 -0
  208. package/screenshots/truth/editor/wait.png +0 -0
  209. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  210. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  211. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  212. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  213. package/src/events.ts +1 -40
  214. package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
  215. package/src/flow/Editor.ts +140 -4
  216. package/src/flow/NodeEditor.ts +1443 -0
  217. package/src/flow/Plumber.ts +0 -9
  218. package/src/flow/actions/add_contact_groups.ts +42 -0
  219. package/src/flow/actions/add_contact_urn.ts +17 -0
  220. package/src/flow/actions/add_input_labels.ts +12 -0
  221. package/src/flow/actions/call_classifier.ts +12 -0
  222. package/src/flow/actions/call_llm.ts +12 -0
  223. package/src/flow/actions/call_resthook.ts +12 -0
  224. package/src/flow/actions/call_webhook.ts +133 -0
  225. package/src/flow/actions/enter_flow.ts +15 -0
  226. package/src/flow/actions/open_ticket.ts +12 -0
  227. package/src/flow/actions/play_audio.ts +12 -0
  228. package/src/flow/actions/remove_contact_groups.ts +66 -0
  229. package/src/flow/actions/request_optin.ts +12 -0
  230. package/src/flow/actions/say_msg.ts +12 -0
  231. package/src/flow/actions/send_broadcast.ts +35 -0
  232. package/src/flow/actions/send_email.ts +60 -0
  233. package/src/flow/actions/send_msg.ts +58 -0
  234. package/src/flow/actions/set_contact_channel.ts +13 -0
  235. package/src/flow/actions/set_contact_field.ts +13 -0
  236. package/src/flow/actions/set_contact_language.ts +11 -0
  237. package/src/flow/actions/set_contact_name.ts +11 -0
  238. package/src/flow/actions/set_contact_status.ts +11 -0
  239. package/src/flow/actions/set_run_result.ts +11 -0
  240. package/src/flow/actions/split_by_expression_example.ts +88 -0
  241. package/src/flow/actions/start_session.ts +12 -0
  242. package/src/flow/actions/transfer_airtime.ts +12 -0
  243. package/src/flow/config.ts +93 -232
  244. package/src/flow/nodes/execute_actions.ts +5 -0
  245. package/src/flow/nodes/split_by_airtime.ts +9 -0
  246. package/src/flow/nodes/split_by_contact_field.ts +7 -0
  247. package/src/flow/nodes/split_by_expression.ts +7 -0
  248. package/src/flow/nodes/split_by_groups.ts +7 -0
  249. package/src/flow/nodes/split_by_random.ts +10 -0
  250. package/src/flow/nodes/split_by_run_result.ts +7 -0
  251. package/src/flow/nodes/split_by_scheme.ts +7 -0
  252. package/src/flow/nodes/split_by_subflow.ts +9 -0
  253. package/src/flow/nodes/split_by_webhook.ts +19 -0
  254. package/src/flow/nodes/wait_for_audio.ts +7 -0
  255. package/src/flow/nodes/wait_for_digits.ts +7 -0
  256. package/src/flow/nodes/wait_for_image.ts +7 -0
  257. package/src/flow/nodes/wait_for_location.ts +7 -0
  258. package/src/flow/nodes/wait_for_menu.ts +7 -0
  259. package/src/flow/nodes/wait_for_response.ts +7 -0
  260. package/src/flow/nodes/wait_for_video.ts +7 -0
  261. package/src/flow/types.ts +352 -0
  262. package/src/flow/utils.ts +76 -0
  263. package/src/form/ArrayEditor.ts +240 -0
  264. package/src/form/BaseListEditor.ts +177 -0
  265. package/src/form/Checkbox.ts +22 -3
  266. package/src/form/Completion.ts +6 -0
  267. package/src/form/FormField.ts +115 -11
  268. package/src/form/KeyValueEditor.ts +251 -0
  269. package/src/form/select/Select.ts +89 -32
  270. package/src/interfaces.ts +7 -2
  271. package/src/live/ContactChat.ts +3 -97
  272. package/src/store/flow-definition.d.ts +6 -1
  273. package/static/api/contacts.json +30 -0
  274. package/static/api/groups.json +4 -426
  275. package/static/api/locations.json +24 -0
  276. package/static/api/media.json +5 -0
  277. package/static/api/optins.json +16 -0
  278. package/static/api/orgs.json +13 -0
  279. package/static/api/topics.json +21 -0
  280. package/static/api/users.json +26 -0
  281. package/static/css/temba-components.css +3 -6
  282. package/temba-modules.ts +9 -2
  283. package/test/ActionHelper.ts +142 -0
  284. package/test/actions/add_contact_groups.test.ts +89 -0
  285. package/test/actions/remove_contact_groups.test.ts +265 -0
  286. package/test/actions/send_email.test.ts +214 -0
  287. package/test/actions/send_msg.test.ts +130 -0
  288. package/test/temba-action-editing-integration.test.ts +240 -0
  289. package/test/temba-checkbox.test.ts +1 -1
  290. package/test/temba-field-config.test.ts +152 -0
  291. package/test/temba-flow-editor-node.test.ts +18 -18
  292. package/test/temba-node-editor.test.ts +353 -0
  293. package/test/temba-select.test.ts +127 -0
  294. package/test-assets/contacts/history.json +11 -33
  295. package/web-dev-server.config.mjs +34 -0
  296. package/.github/workflows/coverage.yml +0 -80
  297. package/demo/sticky-note-demo.html +0 -155
  298. package/out-tsc/src/flow/EditorNode.js.map +0 -1
  299. package/out-tsc/src/flow/render.js +0 -358
  300. package/out-tsc/src/flow/render.js.map +0 -1
  301. package/out-tsc/test/temba-flow-render.test.js +0 -794
  302. package/out-tsc/test/temba-flow-render.test.js.map +0 -1
  303. package/src/flow/render.ts +0 -443
  304. package/test/temba-flow-render.test.ts +0 -1003
@@ -0,0 +1,283 @@
1
+ import '../temba-modules';
2
+ import { html, fixture, expect } from '@open-wc/testing';
3
+ import { assertScreenshot, getClip } from './utils.test';
4
+ const assertDialogScreenshot = async (el, screenshotName) => {
5
+ const dialog = el.shadowRoot
6
+ .querySelector('temba-dialog')
7
+ .shadowRoot.querySelector('.dialog-container');
8
+ await assertScreenshot(screenshotName, getClip(dialog));
9
+ };
10
+ describe('temba-node-editor', () => {
11
+ it('can be created', async () => {
12
+ const el = (await fixture(html `
13
+ <temba-node-editor .isOpen=${true}></temba-node-editor>
14
+ `));
15
+ expect(el).to.exist;
16
+ expect(el.tagName).to.equal('TEMBA-NODE-EDITOR');
17
+ });
18
+ it('renders send_msg action', async () => {
19
+ const action = {
20
+ uuid: 'test-action-uuid',
21
+ type: 'send_msg',
22
+ text: 'Hello world',
23
+ quick_replies: []
24
+ };
25
+ const el = (await fixture(html `
26
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
27
+ `));
28
+ await el.updateComplete;
29
+ expect(el.shadowRoot).to.not.be.null;
30
+ expect(el.action).to.equal(action);
31
+ });
32
+ it('renders set_run_result action', async () => {
33
+ const action = {
34
+ uuid: 'test-action-uuid',
35
+ type: 'set_run_result',
36
+ name: 'result_name',
37
+ value: 'result_value'
38
+ };
39
+ const el = (await fixture(html `
40
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
41
+ `));
42
+ await el.updateComplete;
43
+ expect(el.shadowRoot).to.not.be.null;
44
+ expect(el.action).to.equal(action);
45
+ });
46
+ it('renders set_contact_field action', async () => {
47
+ const action = {
48
+ uuid: 'test-action-uuid',
49
+ type: 'set_contact_field',
50
+ field: { key: 'age', name: 'Age' },
51
+ value: '25'
52
+ };
53
+ const el = (await fixture(html `
54
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
55
+ `));
56
+ await el.updateComplete;
57
+ expect(el.shadowRoot).to.not.be.null;
58
+ expect(el.action).to.equal(action);
59
+ });
60
+ it('renders add_contact_groups action', async () => {
61
+ const action = {
62
+ uuid: 'test-action-uuid',
63
+ type: 'add_contact_groups',
64
+ groups: [{ uuid: 'group-1', name: 'Test Group' }]
65
+ };
66
+ const el = (await fixture(html `
67
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
68
+ `));
69
+ await el.updateComplete;
70
+ expect(el.shadowRoot).to.not.be.null;
71
+ expect(el.action).to.equal(action);
72
+ });
73
+ it('renders enter_flow action', async () => {
74
+ const action = {
75
+ uuid: 'test-action-uuid',
76
+ type: 'enter_flow',
77
+ flow: { uuid: 'flow-1', name: 'Sub Flow' }
78
+ };
79
+ const el = (await fixture(html `
80
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
81
+ `));
82
+ await el.updateComplete;
83
+ expect(el.shadowRoot).to.not.be.null;
84
+ expect(el.action).to.equal(action);
85
+ });
86
+ it('renders node with router configuration', async () => {
87
+ const node = {
88
+ uuid: 'test-node-uuid',
89
+ actions: [],
90
+ exits: [{ uuid: 'exit-1', name: 'Default' }],
91
+ router: {
92
+ type: 'switch',
93
+ result_name: 'result',
94
+ categories: [{ uuid: 'cat-1', name: 'Category 1', exit_uuid: 'exit-1' }]
95
+ }
96
+ };
97
+ const nodeUI = {
98
+ type: 'split_by_expression',
99
+ position: { left: 100, top: 100 }
100
+ };
101
+ const el = (await fixture(html `
102
+ <temba-node-editor
103
+ .node=${node}
104
+ .nodeUI=${nodeUI}
105
+ .isOpen=${true}
106
+ ></temba-node-editor>
107
+ `));
108
+ await el.updateComplete;
109
+ expect(el.shadowRoot).to.not.be.null;
110
+ expect(el.node).to.equal(node);
111
+ expect(el.nodeUI).to.equal(nodeUI);
112
+ await assertDialogScreenshot(el, 'editor/router');
113
+ });
114
+ it('renders node with wait configuration', async () => {
115
+ const node = {
116
+ uuid: 'test-node-uuid',
117
+ actions: [],
118
+ exits: [{ uuid: 'exit-1', name: 'Default' }],
119
+ wait: {
120
+ type: 'msg'
121
+ }
122
+ };
123
+ const nodeUI = {
124
+ type: 'wait_for_response',
125
+ position: { left: 100, top: 100 }
126
+ };
127
+ const el = (await fixture(html `
128
+ <temba-node-editor
129
+ .node=${node}
130
+ .nodeUI=${nodeUI}
131
+ .isOpen=${true}
132
+ ></temba-node-editor>
133
+ `));
134
+ await el.updateComplete;
135
+ expect(el.shadowRoot).to.not.be.null;
136
+ expect(el.node).to.equal(node);
137
+ expect(el.nodeUI).to.equal(nodeUI);
138
+ await assertDialogScreenshot(el, 'editor/wait');
139
+ });
140
+ it('handles different button actions', async () => {
141
+ const action = {
142
+ uuid: 'test-action-uuid',
143
+ type: 'send_msg',
144
+ text: 'Hello world',
145
+ quick_replies: []
146
+ };
147
+ const el = (await fixture(html `
148
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
149
+ `));
150
+ await el.updateComplete;
151
+ let saveEventFired = false;
152
+ let cancelEventFired = false;
153
+ el.addEventListener('temba-action-saved', () => {
154
+ saveEventFired = true;
155
+ });
156
+ el.addEventListener('temba-node-edit-cancelled', () => {
157
+ cancelEventFired = true;
158
+ });
159
+ // Get the dialog element inside the node editor
160
+ const dialog = el.shadowRoot.querySelector('temba-dialog');
161
+ expect(dialog).to.not.be.null;
162
+ // Test Save button by dispatching event on the dialog
163
+ const saveEvent = new CustomEvent('temba-button-clicked', {
164
+ detail: { button: { name: 'Save' } },
165
+ bubbles: true
166
+ });
167
+ dialog.dispatchEvent(saveEvent);
168
+ expect(saveEventFired).to.equal(true);
169
+ // Reset for cancel test
170
+ saveEventFired = false;
171
+ // Test Cancel button
172
+ const cancelEvent = new CustomEvent('temba-button-clicked', {
173
+ detail: { button: { name: 'Cancel' } },
174
+ bubbles: true
175
+ });
176
+ dialog.dispatchEvent(cancelEvent);
177
+ expect(cancelEventFired).to.equal(true);
178
+ });
179
+ it('handles property updates', async () => {
180
+ const el = (await fixture(html `
181
+ <temba-node-editor .isOpen=${true}></temba-node-editor>
182
+ `));
183
+ // Test action property update
184
+ const action = {
185
+ uuid: 'test-action-uuid',
186
+ type: 'send_msg',
187
+ text: 'Hello world',
188
+ quick_replies: []
189
+ };
190
+ el.action = action;
191
+ await el.updateComplete;
192
+ expect(el.action).to.equal(action);
193
+ // Test node property update
194
+ const node = {
195
+ uuid: 'test-node-uuid',
196
+ actions: [],
197
+ exits: []
198
+ };
199
+ el.node = node;
200
+ await el.updateComplete;
201
+ expect(el.node).to.equal(node);
202
+ // Test nodeUI property update
203
+ const nodeUI = {
204
+ type: 'execute_actions',
205
+ position: { left: 100, top: 100 }
206
+ };
207
+ el.nodeUI = nodeUI;
208
+ await el.updateComplete;
209
+ expect(el.nodeUI).to.equal(nodeUI);
210
+ });
211
+ it('handles form submission events', async () => {
212
+ const action = {
213
+ uuid: 'test-action-uuid',
214
+ type: 'send_msg',
215
+ text: 'Hello world',
216
+ quick_replies: []
217
+ };
218
+ const el = (await fixture(html `
219
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
220
+ `));
221
+ await el.updateComplete;
222
+ // Since the form submission handling is complex and involves internal components,
223
+ // we'll just verify the component renders without errors and has the expected structure
224
+ const shadowRoot = el.shadowRoot;
225
+ expect(shadowRoot).to.not.be.null;
226
+ // Verify dialog is present
227
+ const dialog = shadowRoot.querySelector('temba-dialog');
228
+ expect(dialog).to.not.be.null;
229
+ });
230
+ it('handles form validation', async () => {
231
+ const action = {
232
+ uuid: 'test-action-uuid',
233
+ type: 'send_msg',
234
+ text: 'Hello world',
235
+ quick_replies: []
236
+ };
237
+ const el = (await fixture(html `
238
+ <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
239
+ `));
240
+ await el.updateComplete;
241
+ // Test that the component renders form elements
242
+ const shadowRoot = el.shadowRoot;
243
+ expect(shadowRoot).to.not.be.null;
244
+ });
245
+ it('renders different action types correctly', async () => {
246
+ const actionTypes = [
247
+ {
248
+ type: 'send_msg',
249
+ data: { text: 'Message', quick_replies: [] }
250
+ },
251
+ {
252
+ type: 'set_run_result',
253
+ data: { name: 'result', value: 'value' }
254
+ },
255
+ {
256
+ type: 'set_contact_name',
257
+ data: { name: 'John Doe' }
258
+ },
259
+ {
260
+ type: 'set_contact_language',
261
+ data: { language: 'eng' }
262
+ }
263
+ ];
264
+ for (const actionType of actionTypes) {
265
+ const action = {
266
+ uuid: `test-${actionType.type}`,
267
+ type: actionType.type,
268
+ ...actionType.data
269
+ };
270
+ const el = (await fixture(html `
271
+ <temba-node-editor
272
+ .action=${action}
273
+ .isOpen=${true}
274
+ ></temba-node-editor>
275
+ `));
276
+ await el.updateComplete;
277
+ expect(el.shadowRoot).to.not.be.null;
278
+ expect(el.action.type).to.equal(actionType.type);
279
+ await assertDialogScreenshot(el, `editor/${actionType.type}`);
280
+ }
281
+ });
282
+ });
283
+ //# sourceMappingURL=temba-node-editor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temba-node-editor.test.js","sourceRoot":"","sources":["../../test/temba-node-editor.test.ts"],"names":[],"mappings":"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","sourcesContent":["import '../temba-modules';\nimport { html, fixture, expect } from '@open-wc/testing';\nimport { assertScreenshot, getClip } from './utils.test';\n\n// Define interface for NodeEditor component\ninterface NodeEditorElement extends HTMLElement {\n action?: any;\n node?: any;\n nodeUI?: any;\n isOpen?: boolean;\n updateComplete: Promise<boolean>;\n}\n\nconst assertDialogScreenshot = async (\n el: NodeEditorElement,\n screenshotName: string\n) => {\n const dialog = el.shadowRoot\n .querySelector('temba-dialog')\n .shadowRoot.querySelector('.dialog-container') as HTMLElement;\n await assertScreenshot(screenshotName, getClip(dialog));\n};\n\ndescribe('temba-node-editor', () => {\n it('can be created', async () => {\n const el = (await fixture(html`\n <temba-node-editor .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n expect(el).to.exist;\n expect(el.tagName).to.equal('TEMBA-NODE-EDITOR');\n });\n\n it('renders send_msg action', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'send_msg',\n text: 'Hello world',\n quick_replies: []\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action).to.equal(action);\n });\n\n it('renders set_run_result action', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'set_run_result',\n name: 'result_name',\n value: 'result_value'\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action).to.equal(action);\n });\n\n it('renders set_contact_field action', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'set_contact_field',\n field: { key: 'age', name: 'Age' },\n value: '25'\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action).to.equal(action);\n });\n\n it('renders add_contact_groups action', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'add_contact_groups',\n groups: [{ uuid: 'group-1', name: 'Test Group' }]\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action).to.equal(action);\n });\n\n it('renders enter_flow action', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'enter_flow',\n flow: { uuid: 'flow-1', name: 'Sub Flow' }\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action).to.equal(action);\n });\n\n it('renders node with router configuration', async () => {\n const node = {\n uuid: 'test-node-uuid',\n actions: [],\n exits: [{ uuid: 'exit-1', name: 'Default' }],\n router: {\n type: 'switch',\n result_name: 'result',\n categories: [{ uuid: 'cat-1', name: 'Category 1', exit_uuid: 'exit-1' }]\n }\n };\n\n const nodeUI = {\n type: 'split_by_expression',\n position: { left: 100, top: 100 }\n };\n\n const el = (await fixture(html`\n <temba-node-editor\n .node=${node}\n .nodeUI=${nodeUI}\n .isOpen=${true}\n ></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.node).to.equal(node);\n expect(el.nodeUI).to.equal(nodeUI);\n\n await assertDialogScreenshot(el, 'editor/router');\n });\n\n it('renders node with wait configuration', async () => {\n const node = {\n uuid: 'test-node-uuid',\n actions: [],\n exits: [{ uuid: 'exit-1', name: 'Default' }],\n wait: {\n type: 'msg'\n }\n };\n\n const nodeUI = {\n type: 'wait_for_response',\n position: { left: 100, top: 100 }\n };\n\n const el = (await fixture(html`\n <temba-node-editor\n .node=${node}\n .nodeUI=${nodeUI}\n .isOpen=${true}\n ></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.node).to.equal(node);\n expect(el.nodeUI).to.equal(nodeUI);\n\n await assertDialogScreenshot(el, 'editor/wait');\n });\n\n it('handles different button actions', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'send_msg',\n text: 'Hello world',\n quick_replies: []\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n\n let saveEventFired = false;\n let cancelEventFired = false;\n\n el.addEventListener('temba-action-saved', () => {\n saveEventFired = true;\n });\n\n el.addEventListener('temba-node-edit-cancelled', () => {\n cancelEventFired = true;\n });\n\n // Get the dialog element inside the node editor\n const dialog = el.shadowRoot!.querySelector('temba-dialog');\n expect(dialog).to.not.be.null;\n\n // Test Save button by dispatching event on the dialog\n const saveEvent = new CustomEvent('temba-button-clicked', {\n detail: { button: { name: 'Save' } },\n bubbles: true\n });\n dialog!.dispatchEvent(saveEvent);\n expect(saveEventFired).to.equal(true);\n\n // Reset for cancel test\n saveEventFired = false;\n\n // Test Cancel button\n const cancelEvent = new CustomEvent('temba-button-clicked', {\n detail: { button: { name: 'Cancel' } },\n bubbles: true\n });\n dialog!.dispatchEvent(cancelEvent);\n expect(cancelEventFired).to.equal(true);\n });\n\n it('handles property updates', async () => {\n const el = (await fixture(html`\n <temba-node-editor .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n // Test action property update\n const action = {\n uuid: 'test-action-uuid',\n type: 'send_msg',\n text: 'Hello world',\n quick_replies: []\n };\n\n el.action = action;\n await el.updateComplete;\n expect(el.action).to.equal(action);\n\n // Test node property update\n const node = {\n uuid: 'test-node-uuid',\n actions: [],\n exits: []\n };\n\n el.node = node;\n await el.updateComplete;\n expect(el.node).to.equal(node);\n\n // Test nodeUI property update\n const nodeUI = {\n type: 'execute_actions',\n position: { left: 100, top: 100 }\n };\n\n el.nodeUI = nodeUI;\n await el.updateComplete;\n expect(el.nodeUI).to.equal(nodeUI);\n });\n\n it('handles form submission events', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'send_msg',\n text: 'Hello world',\n quick_replies: []\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n\n // Since the form submission handling is complex and involves internal components,\n // we'll just verify the component renders without errors and has the expected structure\n const shadowRoot = el.shadowRoot;\n expect(shadowRoot).to.not.be.null;\n\n // Verify dialog is present\n const dialog = shadowRoot!.querySelector('temba-dialog');\n expect(dialog).to.not.be.null;\n });\n\n it('handles form validation', async () => {\n const action = {\n uuid: 'test-action-uuid',\n type: 'send_msg',\n text: 'Hello world',\n quick_replies: []\n };\n\n const el = (await fixture(html`\n <temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n\n // Test that the component renders form elements\n const shadowRoot = el.shadowRoot;\n expect(shadowRoot).to.not.be.null;\n });\n\n it('renders different action types correctly', async () => {\n const actionTypes = [\n {\n type: 'send_msg',\n data: { text: 'Message', quick_replies: [] }\n },\n {\n type: 'set_run_result',\n data: { name: 'result', value: 'value' }\n },\n {\n type: 'set_contact_name',\n data: { name: 'John Doe' }\n },\n {\n type: 'set_contact_language',\n data: { language: 'eng' }\n }\n ];\n\n for (const actionType of actionTypes) {\n const action = {\n uuid: `test-${actionType.type}`,\n type: actionType.type,\n ...actionType.data\n };\n\n const el = (await fixture(html`\n <temba-node-editor\n .action=${action}\n .isOpen=${true}\n ></temba-node-editor>\n `)) as NodeEditorElement;\n\n await el.updateComplete;\n expect(el.shadowRoot).to.not.be.null;\n expect(el.action.type).to.equal(actionType.type);\n\n await assertDialogScreenshot(el, `editor/${actionType.type}`);\n }\n });\n});\n"]}
@@ -393,6 +393,91 @@ describe('temba-select', () => {
393
393
  expect(itemText).to.not.contain('Red');
394
394
  });
395
395
  });
396
+ describe('emails functionality', () => {
397
+ it('only allows valid email addresses as options', async () => {
398
+ const select = await createSelect(clock, getSelectHTML([], {
399
+ placeholder: 'Enter email addresses',
400
+ searchable: true,
401
+ emails: true
402
+ }));
403
+ // Try typing an invalid email - should not show as option
404
+ await typeInto('temba-select', 'invalid-email', false, false);
405
+ await clock.runAll();
406
+ await select.updateComplete;
407
+ let visibleOptions = select.shadowRoot.querySelectorAll('.option:not(.header)');
408
+ expect(visibleOptions.length).to.equal(0);
409
+ // Clear input
410
+ select.input = '';
411
+ await select.updateComplete;
412
+ // Try typing a valid email - should show as option
413
+ await typeInto('temba-select', 'test@example.com', false, false);
414
+ await clock.runAll();
415
+ await select.updateComplete;
416
+ await openSelect(clock, select);
417
+ const optionsComponent = select.shadowRoot.querySelector('temba-options');
418
+ visibleOptions = optionsComponent.shadowRoot.querySelectorAll('.option:not(.header)');
419
+ expect(visibleOptions.length).to.equal(1);
420
+ expect(visibleOptions[0].textContent).to.contain('test@example.com');
421
+ });
422
+ it('behaves as multi-select when emails is true', async () => {
423
+ const select = await createSelect(clock, getSelectHTML([], {
424
+ placeholder: 'Enter email addresses',
425
+ searchable: true,
426
+ emails: true
427
+ }));
428
+ // Add first email
429
+ await typeInto('temba-select', 'first@example.com', false, false);
430
+ await clock.runAll();
431
+ await select.updateComplete;
432
+ // Click on the first option to select it using the standard helper
433
+ await openAndClick(clock, select, 0);
434
+ expect(select.values.length).to.equal(1);
435
+ expect(select.values[0].value).to.equal('first@example.com');
436
+ // Add second email
437
+ await typeInto('temba-select', 'second@example.com', false, false);
438
+ await clock.runAll();
439
+ await select.updateComplete;
440
+ // Click on the second option to select it using the standard helper
441
+ await openAndClick(clock, select, 0);
442
+ // Should have both emails selected (multi-select behavior)
443
+ expect(select.values.length).to.equal(2);
444
+ expect(select.values[0].value).to.equal('first@example.com');
445
+ expect(select.values[1].value).to.equal('second@example.com');
446
+ });
447
+ it('validates email format correctly', async () => {
448
+ const select = await createSelect(clock, getSelectHTML([], {
449
+ placeholder: 'Enter email addresses',
450
+ searchable: true,
451
+ emails: true
452
+ }));
453
+ // Test various email formats
454
+ const testCases = [
455
+ { email: 'valid@example.com', shouldBeValid: true },
456
+ { email: 'user.name+tag@example.co.uk', shouldBeValid: true },
457
+ { email: 'invalid-email', shouldBeValid: false },
458
+ { email: '@example.com', shouldBeValid: false },
459
+ { email: 'user@', shouldBeValid: false },
460
+ { email: 'user name@example.com', shouldBeValid: false }, // space not allowed
461
+ { email: 'user@example', shouldBeValid: false } // no domain extension
462
+ ];
463
+ for (const testCase of testCases) {
464
+ select.input = '';
465
+ await select.updateComplete;
466
+ await typeInto('temba-select', testCase.email, false, false);
467
+ await clock.runAll();
468
+ await select.updateComplete;
469
+ await openSelect(clock, select);
470
+ const optionsComponent = select.shadowRoot.querySelector('temba-options');
471
+ const visibleOptions = optionsComponent.shadowRoot.querySelectorAll('.option:not(.header)');
472
+ if (testCase.shouldBeValid) {
473
+ expect(visibleOptions.length, `${testCase.email} should be valid`).to.equal(1);
474
+ }
475
+ else {
476
+ expect(visibleOptions.length, `${testCase.email} should be invalid`).to.equal(0);
477
+ }
478
+ }
479
+ });
480
+ });
396
481
  describe('static options', () => {
397
482
  it('accepts an initial value', async () => {
398
483
  const select = await createSelect(clock, getSelectHTML(colors, { value: '1' }));