@nyaruka/temba-components 0.140.0 → 0.141.1

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 (297) hide show
  1. package/.lintstagedrc.js +10 -0
  2. package/CHANGELOG.md +22 -0
  3. package/dist/locales/es.js +5 -5
  4. package/dist/locales/es.js.map +1 -1
  5. package/dist/locales/fr.js +5 -5
  6. package/dist/locales/fr.js.map +1 -1
  7. package/dist/locales/locale-codes.js +11 -2
  8. package/dist/locales/locale-codes.js.map +1 -1
  9. package/dist/locales/pt.js +5 -5
  10. package/dist/locales/pt.js.map +1 -1
  11. package/dist/temba-components.js +263 -156
  12. package/dist/temba-components.js.map +1 -1
  13. package/out-tsc/src/display/FloatingTab.js +1 -1
  14. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  15. package/out-tsc/src/flow/CanvasNode.js +1 -1
  16. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  17. package/out-tsc/src/flow/Editor.js +239 -43
  18. package/out-tsc/src/flow/Editor.js.map +1 -1
  19. package/out-tsc/src/flow/Plumber.js +61 -14
  20. package/out-tsc/src/flow/Plumber.js.map +1 -1
  21. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
  22. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
  24. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  25. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
  26. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  27. package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
  28. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
  30. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  31. package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
  32. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  33. package/out-tsc/src/flow/actions/start_session.js +10 -3
  34. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  35. package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
  36. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  37. package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
  38. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  39. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
  40. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  41. package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
  42. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  43. package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
  44. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/wait_for_digits.js +1 -1
  46. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  48. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  49. package/out-tsc/src/form/FieldRenderer.js +7 -0
  50. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  51. package/out-tsc/src/layout/Dialog.js +0 -1
  52. package/out-tsc/src/layout/Dialog.js.map +1 -1
  53. package/out-tsc/src/layout/Modax.js +20 -2
  54. package/out-tsc/src/layout/Modax.js.map +1 -1
  55. package/out-tsc/src/list/ContentMenu.js +14 -1
  56. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  57. package/out-tsc/src/live/ContactChat.js +10 -1
  58. package/out-tsc/src/live/ContactChat.js.map +1 -1
  59. package/out-tsc/src/live/TembaChart.js.map +1 -1
  60. package/out-tsc/src/locales/es.js +5 -5
  61. package/out-tsc/src/locales/es.js.map +1 -1
  62. package/out-tsc/src/locales/fr.js +5 -5
  63. package/out-tsc/src/locales/fr.js.map +1 -1
  64. package/out-tsc/src/locales/locale-codes.js +11 -2
  65. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  66. package/out-tsc/src/locales/pt.js +5 -5
  67. package/out-tsc/src/locales/pt.js.map +1 -1
  68. package/out-tsc/src/simulator/Simulator.js +11 -0
  69. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  70. package/out-tsc/src/store/AppState.js +13 -0
  71. package/out-tsc/src/store/AppState.js.map +1 -1
  72. package/out-tsc/src/version.js +9 -0
  73. package/out-tsc/src/version.js.map +1 -0
  74. package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
  75. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
  76. package/out-tsc/test/actions/add_input_labels.test.js +53 -0
  77. package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
  78. package/out-tsc/test/actions/enter_flow.test.js +71 -0
  79. package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
  80. package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
  81. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
  82. package/out-tsc/test/actions/send_broadcast.test.js +41 -0
  83. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  84. package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
  85. package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
  86. package/out-tsc/test/actions/set_contact_field.test.js +52 -0
  87. package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
  88. package/out-tsc/test/actions/set_contact_language.test.js +39 -0
  89. package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
  90. package/out-tsc/test/actions/set_contact_name.test.js +28 -0
  91. package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
  92. package/out-tsc/test/actions/set_contact_status.test.js +44 -0
  93. package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
  94. package/out-tsc/test/actions/set_run_result.test.js +47 -0
  95. package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
  96. package/out-tsc/test/actions/start_session.test.js +76 -0
  97. package/out-tsc/test/actions/start_session.test.js.map +1 -1
  98. package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
  99. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
  100. package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
  101. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
  102. package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
  103. package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
  104. package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
  105. package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
  106. package/out-tsc/test/temba-contact-chat.test.js +12 -0
  107. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  108. package/out-tsc/test/temba-flow-editor.test.js +206 -0
  109. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  110. package/out-tsc/test/temba-flow-plumber.test.js +19 -0
  111. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  112. package/out-tsc/test/temba-select.test.js +4 -1
  113. package/out-tsc/test/temba-select.test.js.map +1 -1
  114. package/out-tsc/test/utils.test.js +4 -2
  115. package/out-tsc/test/utils.test.js.map +1 -1
  116. package/package.json +3 -9
  117. package/rollup.components.mjs +7 -1
  118. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  119. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  120. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  121. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  122. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  123. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  124. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  125. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  126. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  127. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  128. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  129. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  130. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  131. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  132. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  133. package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
  134. package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
  135. package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
  136. package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
  137. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  138. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  139. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  140. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  141. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  142. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  143. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  144. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  145. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  146. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  147. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  148. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  149. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  150. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  151. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  152. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  153. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  154. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  155. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  156. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  157. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  158. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  159. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  160. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  161. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  162. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  163. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  164. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  165. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  166. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  167. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  168. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  169. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  170. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  171. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  172. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  173. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  174. package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
  175. package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
  176. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  177. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  178. package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
  179. package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
  180. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  181. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  182. package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
  183. package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
  184. package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
  185. package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
  186. package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
  187. package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
  188. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  189. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  190. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  191. package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
  192. package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
  193. package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
  194. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  195. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  196. package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
  197. package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
  198. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  199. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  200. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  201. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  202. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  203. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  204. package/screenshots/truth/editor/wait.png +0 -0
  205. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  206. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  207. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  208. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  209. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  210. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  211. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  212. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  213. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  214. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  215. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  216. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  217. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  218. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  219. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  220. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  221. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  222. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  223. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  224. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  225. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  226. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  227. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  228. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  229. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  230. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  231. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  232. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  233. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  234. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  235. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  236. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  237. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  238. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  239. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  240. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  241. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  242. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  243. package/src/display/FloatingTab.ts +1 -1
  244. package/src/flow/CanvasNode.ts +1 -1
  245. package/src/flow/Editor.ts +299 -88
  246. package/src/flow/Plumber.ts +89 -14
  247. package/src/flow/actions/add_contact_groups.ts +4 -1
  248. package/src/flow/actions/add_input_labels.ts +4 -1
  249. package/src/flow/actions/remove_contact_groups.ts +6 -1
  250. package/src/flow/actions/send_broadcast.ts +6 -2
  251. package/src/flow/actions/set_contact_channel.ts +13 -1
  252. package/src/flow/actions/set_contact_status.ts +7 -5
  253. package/src/flow/actions/start_session.ts +10 -3
  254. package/src/flow/nodes/split_by_contact_field.ts +16 -5
  255. package/src/flow/nodes/split_by_expression.ts +1 -1
  256. package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
  257. package/src/flow/nodes/split_by_random.ts +0 -1
  258. package/src/flow/nodes/split_by_run_result.ts +10 -4
  259. package/src/flow/nodes/wait_for_digits.ts +2 -1
  260. package/src/flow/nodes/wait_for_response.ts +1 -1
  261. package/src/form/FieldRenderer.ts +7 -0
  262. package/src/layout/Dialog.ts +0 -1
  263. package/src/layout/Modax.ts +19 -2
  264. package/src/list/ContentMenu.ts +15 -1
  265. package/src/live/ContactChat.ts +10 -1
  266. package/src/live/TembaChart.ts +1 -1
  267. package/src/locales/es.ts +18 -13
  268. package/src/locales/fr.ts +18 -13
  269. package/src/locales/locale-codes.ts +11 -2
  270. package/src/locales/pt.ts +18 -13
  271. package/src/simulator/Simulator.ts +12 -0
  272. package/src/store/AppState.ts +15 -0
  273. package/src/store/flow-definition.d.ts +1 -0
  274. package/src/version.ts +10 -0
  275. package/test/actions/add_contact_groups.test.ts +38 -0
  276. package/test/actions/add_input_labels.test.ts +67 -0
  277. package/test/actions/enter_flow.test.ts +88 -0
  278. package/test/actions/remove_contact_groups.test.ts +29 -0
  279. package/test/actions/send_broadcast.test.ts +44 -0
  280. package/test/actions/set_contact_channel.test.ts +88 -0
  281. package/test/actions/set_contact_field.test.ts +68 -0
  282. package/test/actions/set_contact_language.test.ts +55 -0
  283. package/test/actions/set_contact_name.test.ts +39 -0
  284. package/test/actions/set_contact_status.test.ts +64 -0
  285. package/test/actions/set_run_result.test.ts +61 -0
  286. package/test/actions/start_session.test.ts +82 -0
  287. package/test/nodes/split_by_contact_field.test.ts +59 -0
  288. package/test/nodes/split_by_run_result.test.ts +100 -0
  289. package/test/nodes/split_by_ticket.test.ts +157 -0
  290. package/test/nodes/split_by_webhook.test.ts +131 -0
  291. package/test/temba-contact-chat.test.ts +17 -0
  292. package/test/temba-flow-editor.test.ts +264 -0
  293. package/test/temba-flow-plumber.test.ts +62 -0
  294. package/test/temba-select.test.ts +6 -1
  295. package/test/utils.test.ts +4 -2
  296. package/web-dev-server.config.mjs +5 -1
  297. package/web-test-runner.config.mjs +4 -1
@@ -2,6 +2,8 @@ import { html, fixture, expect } from '@open-wc/testing';
2
2
  import { Editor } from '../src/flow/Editor';
3
3
  import { Plumber } from '../src/flow/Plumber';
4
4
  import { stub, restore } from 'sinon';
5
+ import { zustand } from '../src/store/AppState';
6
+ import { TEMBA_COMPONENTS_VERSION } from '../src/version';
5
7
 
6
8
  // Register the component
7
9
  customElements.define('temba-flow-editor', Editor);
@@ -122,6 +124,46 @@ describe('Editor', () => {
122
124
  });
123
125
  });
124
126
 
127
+ describe('disconnectedCallback', () => {
128
+ it('clears flow data from the store when editor is removed', async () => {
129
+ // Set up some flow-specific state
130
+ zustand.setState({
131
+ flowDefinition: { nodes: [], _ui: { nodes: {} } } as any,
132
+ activity: { nodes: {}, segments: {} },
133
+ simulatorActivity: { nodes: {}, segments: {} },
134
+ simulatorActive: true,
135
+ flowInfo: {
136
+ results: [],
137
+ dependencies: [],
138
+ counts: { nodes: 0, languages: 0 },
139
+ locals: []
140
+ } as any,
141
+ dirtyDate: new Date()
142
+ });
143
+
144
+ editor = await fixture(html`
145
+ <temba-flow-editor>
146
+ <div id="canvas"></div>
147
+ </temba-flow-editor>
148
+ `);
149
+
150
+ // Verify state is populated
151
+ expect(zustand.getState().flowDefinition).to.not.be.null;
152
+ expect(zustand.getState().activity).to.not.be.null;
153
+
154
+ // Remove the editor from the DOM
155
+ editor.remove();
156
+
157
+ // Verify all flow-specific state has been cleared
158
+ expect(zustand.getState().flowDefinition).to.be.null;
159
+ expect(zustand.getState().activity).to.be.null;
160
+ expect(zustand.getState().simulatorActivity).to.be.null;
161
+ expect(zustand.getState().simulatorActive).to.be.false;
162
+ expect(zustand.getState().flowInfo).to.be.null;
163
+ expect(zustand.getState().dirtyDate).to.be.null;
164
+ });
165
+ });
166
+
125
167
  describe('render method', () => {
126
168
  it('renders loading when no definition', async () => {
127
169
  editor = await fixture(html`
@@ -822,4 +864,226 @@ describe('Editor', () => {
822
864
  expect(flowNodes[0].classList.contains('flow-start')).to.be.true;
823
865
  });
824
866
  });
867
+
868
+ describe('save feedback', () => {
869
+ let mockPostJSON: any;
870
+ let storeElement: HTMLElement;
871
+
872
+ before(() => {
873
+ // Create a mock temba-store element that getStore() will find
874
+ // Use the real zustand getState so all store interactions work
875
+ storeElement = document.createElement('temba-store');
876
+ (storeElement as any).getState = () => zustand.getState();
877
+ document.body.appendChild(storeElement);
878
+ });
879
+
880
+ after(() => {
881
+ storeElement.remove();
882
+ });
883
+
884
+ beforeEach(() => {
885
+ mockPostJSON = stub();
886
+ (storeElement as any).postJSON = mockPostJSON;
887
+ });
888
+
889
+ afterEach(() => {
890
+ // Clean up any dialogs left in the DOM
891
+ document.querySelectorAll('temba-dialog').forEach((d) => d.remove());
892
+ });
893
+
894
+ it('sets isSaving when dirtyDate changes', async () => {
895
+ editor = await fixture(html`
896
+ <temba-flow-editor>
897
+ <div id="canvas"></div>
898
+ </temba-flow-editor>
899
+ `);
900
+
901
+ (editor as any).isSaving = false;
902
+
903
+ // Simulate a dirtyDate change via updated()
904
+ (editor as any).dirtyDate = new Date();
905
+ const changes = new Map();
906
+ changes.set('dirtyDate', null);
907
+ (editor as any).updated(changes);
908
+
909
+ expect((editor as any).isSaving).to.be.true;
910
+ });
911
+
912
+ it('renders save indicator with visible class when saving', async () => {
913
+ editor = await fixture(html`
914
+ <temba-flow-editor>
915
+ <div id="canvas"></div>
916
+ </temba-flow-editor>
917
+ `);
918
+
919
+ (editor as any).canvasSize = { width: 800, height: 600 };
920
+ (editor as any).isSaving = true;
921
+ await editor.updateComplete;
922
+
923
+ const indicator = editor.querySelector('.save-indicator');
924
+ expect(indicator).to.exist;
925
+ expect(indicator.classList.contains('visible')).to.be.true;
926
+ });
927
+
928
+ it('save indicator is not visible when not saving', async () => {
929
+ editor = await fixture(html`
930
+ <temba-flow-editor>
931
+ <div id="canvas"></div>
932
+ </temba-flow-editor>
933
+ `);
934
+
935
+ (editor as any).canvasSize = { width: 800, height: 600 };
936
+ (editor as any).isSaving = false;
937
+ await editor.updateComplete;
938
+
939
+ const indicator = editor.querySelector('.save-indicator');
940
+ expect(indicator).to.exist;
941
+ expect(indicator.classList.contains('visible')).to.be.false;
942
+ });
943
+
944
+ it('clears isSaving after successful save', async () => {
945
+ editor = await fixture(html`
946
+ <temba-flow-editor>
947
+ <div id="canvas"></div>
948
+ </temba-flow-editor>
949
+ `);
950
+
951
+ editor.flow = 'test-flow';
952
+ (editor as any).definition = { nodes: [], _ui: { nodes: {} } };
953
+
954
+ mockPostJSON.resolves({
955
+ status: 200,
956
+ json: {},
957
+ body: '{}',
958
+ headers: new Headers()
959
+ });
960
+
961
+ await (editor as any).saveChanges();
962
+
963
+ expect((editor as any).isSaving).to.be.false;
964
+ });
965
+
966
+ it('adds temba-components version under _ui.editor when saving', async () => {
967
+ editor = await fixture(html`
968
+ <temba-flow-editor>
969
+ <div id="canvas"></div>
970
+ </temba-flow-editor>
971
+ `);
972
+
973
+ editor.flow = 'test-flow';
974
+ (editor as any).definition = { nodes: [], _ui: { nodes: {} } };
975
+
976
+ mockPostJSON.resolves({
977
+ status: 200,
978
+ json: {},
979
+ body: '{}',
980
+ headers: new Headers()
981
+ });
982
+
983
+ await (editor as any).saveChanges();
984
+
985
+ const payload = mockPostJSON.firstCall.args[1];
986
+ expect(payload._ui.editor).to.equal(TEMBA_COMPONENTS_VERSION);
987
+ });
988
+
989
+ it('shows error dialog on non-200 response', async () => {
990
+ editor = await fixture(html`
991
+ <temba-flow-editor>
992
+ <div id="canvas"></div>
993
+ </temba-flow-editor>
994
+ `);
995
+
996
+ editor.flow = 'test-flow';
997
+ (editor as any).definition = { nodes: [], _ui: { nodes: {} } };
998
+
999
+ mockPostJSON.resolves({
1000
+ status: 400,
1001
+ json: { detail: 'Invalid flow definition' },
1002
+ body: '{"detail":"Invalid flow definition"}',
1003
+ headers: new Headers()
1004
+ });
1005
+
1006
+ await (editor as any).saveChanges();
1007
+ await editor.updateComplete;
1008
+
1009
+ expect((editor as any).isSaving).to.be.false;
1010
+ const dialog = document.querySelector('temba-dialog');
1011
+ expect(dialog).to.exist;
1012
+ expect(dialog.textContent).to.contain('Invalid flow definition');
1013
+ });
1014
+
1015
+ it('shows error dialog on 500 server error', async () => {
1016
+ editor = await fixture(html`
1017
+ <temba-flow-editor>
1018
+ <div id="canvas"></div>
1019
+ </temba-flow-editor>
1020
+ `);
1021
+
1022
+ editor.flow = 'test-flow';
1023
+ (editor as any).definition = { nodes: [], _ui: { nodes: {} } };
1024
+
1025
+ mockPostJSON.rejects(new Response(null, { status: 500 }));
1026
+
1027
+ await (editor as any).saveChanges();
1028
+ await editor.updateComplete;
1029
+
1030
+ expect((editor as any).isSaving).to.be.false;
1031
+ const dialog = document.querySelector('temba-dialog');
1032
+ expect(dialog).to.exist;
1033
+ expect(dialog.textContent).to.contain('Server error');
1034
+ });
1035
+
1036
+ it('shows error dialog on network failure', async () => {
1037
+ editor = await fixture(html`
1038
+ <temba-flow-editor>
1039
+ <div id="canvas"></div>
1040
+ </temba-flow-editor>
1041
+ `);
1042
+
1043
+ editor.flow = 'test-flow';
1044
+ (editor as any).definition = { nodes: [], _ui: { nodes: {} } };
1045
+
1046
+ mockPostJSON.rejects(new Error('Network error'));
1047
+
1048
+ await (editor as any).saveChanges();
1049
+ await editor.updateComplete;
1050
+
1051
+ expect((editor as any).isSaving).to.be.false;
1052
+ const dialog = document.querySelector('temba-dialog');
1053
+ expect(dialog).to.exist;
1054
+ expect(dialog.textContent).to.contain('Unable to reach the server');
1055
+ });
1056
+
1057
+ it('extracts error message from response json fields', () => {
1058
+ editor = new Editor();
1059
+
1060
+ expect(
1061
+ (editor as any).extractErrorMessage({
1062
+ status: 400,
1063
+ json: { detail: 'Bad request' }
1064
+ })
1065
+ ).to.equal('Bad request');
1066
+
1067
+ expect(
1068
+ (editor as any).extractErrorMessage({
1069
+ status: 400,
1070
+ json: { error: 'Something went wrong' }
1071
+ })
1072
+ ).to.equal('Something went wrong');
1073
+
1074
+ expect(
1075
+ (editor as any).extractErrorMessage({
1076
+ status: 400,
1077
+ json: { description: 'Detailed error' }
1078
+ })
1079
+ ).to.equal('Detailed error');
1080
+
1081
+ expect(
1082
+ (editor as any).extractErrorMessage({
1083
+ status: 403,
1084
+ json: {}
1085
+ })
1086
+ ).to.equal('Save failed with status 403.');
1087
+ });
1088
+ });
825
1089
  });
@@ -171,4 +171,66 @@ describe('calculateFlowchartPath', () => {
171
171
  expect(path).to.include('M 150 0');
172
172
  expect(path).to.include('L 50 100'); // ends at target
173
173
  });
174
+
175
+ it('applies jogYOffset to shift the horizontal jog level', () => {
176
+ const pathNoOffset = calculateFlowchartPath(
177
+ 50,
178
+ 0,
179
+ 150,
180
+ 200,
181
+ 20,
182
+ 10,
183
+ 5,
184
+ 'top',
185
+ 0
186
+ );
187
+ const pathPosOffset = calculateFlowchartPath(
188
+ 50,
189
+ 0,
190
+ 150,
191
+ 200,
192
+ 20,
193
+ 10,
194
+ 5,
195
+ 'top',
196
+ 10
197
+ );
198
+ const pathNegOffset = calculateFlowchartPath(
199
+ 50,
200
+ 0,
201
+ 150,
202
+ 200,
203
+ 20,
204
+ 10,
205
+ 5,
206
+ 'top',
207
+ -10
208
+ );
209
+ expect(pathNoOffset).to.not.equal(pathPosOffset);
210
+ expect(pathNoOffset).to.not.equal(pathNegOffset);
211
+ expect(pathPosOffset).to.not.equal(pathNegOffset);
212
+ });
213
+
214
+ it('produces same path with jogYOffset=0 as without offset', () => {
215
+ const pathDefault = calculateFlowchartPath(50, 0, 150, 200);
216
+ const pathZero = calculateFlowchartPath(
217
+ 50,
218
+ 0,
219
+ 150,
220
+ 200,
221
+ 20,
222
+ 10,
223
+ 5,
224
+ 'top',
225
+ 0
226
+ );
227
+ expect(pathDefault).to.equal(pathZero);
228
+ });
229
+
230
+ it('clamps jogYOffset to valid bounds', () => {
231
+ // Large positive offset should not push jogY past entryY
232
+ const path = calculateFlowchartPath(50, 0, 150, 50, 20, 10, 5, 'top', 1000);
233
+ expect(path).to.include('M 50 0');
234
+ expect(path).to.include('L 150 50'); // should still reach target
235
+ });
174
236
  });
@@ -857,7 +857,12 @@ describe('temba-select', () => {
857
857
 
858
858
  await openSelect(clock, select);
859
859
  await typeInto('temba-select', 're', false);
860
- await openSelect(clock, select);
860
+
861
+ // Ensure Lit has processed the input change and scheduled the debounced fetch
862
+ await select.updateComplete;
863
+ clock.runAll();
864
+ await select.updateComplete;
865
+
861
866
  assert.equal(select.visibleOptions.length, 2);
862
867
 
863
868
  await assertScreenshot('select/searching', getClipWithOptions(select));
@@ -220,7 +220,9 @@ export const delay = (millis: number) => {
220
220
  });
221
221
  };
222
222
 
223
- // Enhanced wait utility for more robust testing
223
+ // Enhanced wait utility for more robust testing.
224
+ // Uses the Puppeteer-provided waitFor (real time) instead of delay (which uses
225
+ // window.setTimeout and breaks when sinon fake timers are active).
224
226
  export const waitForCondition = async (
225
227
  predicate: () => boolean,
226
228
  maxAttempts: number = 20,
@@ -228,7 +230,7 @@ export const waitForCondition = async (
228
230
  ): Promise<void> => {
229
231
  let attempts = 0;
230
232
  while (!predicate() && attempts < maxAttempts) {
231
- await delay(delayMs);
233
+ await waitFor(delayMs);
232
234
  attempts++;
233
235
  }
234
236
  if (!predicate()) {
@@ -11,6 +11,9 @@ import { generateFlowInfo, handleMinioUpload, handleEntityCreation } from './web
11
11
  const DEV_DATA_DIR = '/tmp/temba-dev-data';
12
12
  const DEV_FLOWS_DIR = path.join(DEV_DATA_DIR, 'flows');
13
13
  const DEV_API_DIR = path.join(DEV_DATA_DIR, 'api');
14
+ const TEMBA_COMPONENTS_VERSION = JSON.parse(
15
+ fs.readFileSync(path.resolve('./package.json'), 'utf-8')
16
+ ).version;
14
17
 
15
18
  // Setup development data directories and copy defaults if needed
16
19
  function setupDevData() {
@@ -226,6 +229,7 @@ export default {
226
229
  replacePlugin({
227
230
  preventAssignment: true,
228
231
  'process.env.NODE_ENV': JSON.stringify('development'),
232
+ '__TEMBA_COMPONENTS_VERSION__': JSON.stringify(TEMBA_COMPONENTS_VERSION),
229
233
  'process.env.MINIO_ENDPOINT': JSON.stringify('http://minio:9000'),
230
234
  'process.env.MINIO_PUBLIC_ENDPOINT': JSON.stringify('http://localhost:9000'),
231
235
  'process.env.MINIO_ACCESS_KEY': JSON.stringify('root'),
@@ -500,4 +504,4 @@ export default {
500
504
  }
501
505
  }
502
506
  ],
503
- };
507
+ };
@@ -15,6 +15,9 @@ import replace from '@rollup/plugin-replace';
15
15
  import { fromRollup } from '@web/dev-server-rollup';
16
16
 
17
17
  const replacePlugin = fromRollup(replace);
18
+ const TEMBA_COMPONENTS_VERSION = JSON.parse(
19
+ fs.readFileSync(path.resolve('./package.json'), 'utf-8')
20
+ ).version;
18
21
 
19
22
  const SCREENSHOTS = 'screenshots';
20
23
  const DIFF = 'diff';
@@ -320,6 +323,7 @@ export default {
320
323
  replacePlugin({
321
324
  preventAssignment: true,
322
325
  'process.env.NODE_ENV': JSON.stringify('test'),
326
+ '__TEMBA_COMPONENTS_VERSION__': JSON.stringify(TEMBA_COMPONENTS_VERSION),
323
327
  }),
324
328
  {
325
329
  name: 'api-mock-server',
@@ -451,4 +455,3 @@ export default {
451
455
  }),
452
456
  ],
453
457
  };
454
-