@nyaruka/temba-components 0.130.1 → 0.130.3

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 (250) hide show
  1. package/CHANGELOG.md +34 -4
  2. package/DEV_DATA.md +89 -0
  3. package/demo/data/flows/food-order.json +4 -4
  4. package/demo/data/flows/sample-flow.json +132 -147
  5. package/dist/temba-components.js +787 -659
  6. package/dist/temba-components.js.map +1 -1
  7. package/out-tsc/src/display/Chat.js +5 -3
  8. package/out-tsc/src/display/Chat.js.map +1 -1
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasNode.js +83 -78
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  12. package/out-tsc/src/flow/Editor.js +1 -0
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +47 -3
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  16. package/out-tsc/src/flow/actions/add_contact_urn.js +1 -1
  17. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  18. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  19. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  20. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  21. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_contact_language.js +3 -1
  23. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  24. package/out-tsc/src/flow/actions/set_contact_name.js +1 -1
  25. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  26. package/out-tsc/src/flow/actions/set_contact_status.js +17 -14
  27. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  28. package/out-tsc/src/flow/actions/set_run_result.js +1 -1
  29. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  30. package/out-tsc/src/flow/nodes/split_by_llm.js +12 -12
  31. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  32. package/out-tsc/src/flow/nodes/wait_for_response.js +609 -6
  33. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  34. package/out-tsc/src/flow/operators.js +194 -0
  35. package/out-tsc/src/flow/operators.js.map +1 -0
  36. package/out-tsc/src/flow/types.js.map +1 -1
  37. package/out-tsc/src/form/ArrayEditor.js +84 -19
  38. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  39. package/out-tsc/src/form/Checkbox.js +12 -0
  40. package/out-tsc/src/form/Checkbox.js.map +1 -1
  41. package/out-tsc/src/form/FieldRenderer.js +13 -3
  42. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  43. package/out-tsc/src/form/TextInput.js +20 -1
  44. package/out-tsc/src/form/TextInput.js.map +1 -1
  45. package/out-tsc/src/form/select/Select.js +7 -0
  46. package/out-tsc/src/form/select/Select.js.map +1 -1
  47. package/out-tsc/src/interfaces.js.map +1 -1
  48. package/out-tsc/src/layout/Dialog.js +3 -4
  49. package/out-tsc/src/layout/Dialog.js.map +1 -1
  50. package/out-tsc/src/list/RunList.js +2 -2
  51. package/out-tsc/src/list/RunList.js.map +1 -1
  52. package/out-tsc/src/live/ContactChat.js +101 -44
  53. package/out-tsc/src/live/ContactChat.js.map +1 -1
  54. package/out-tsc/src/live/ContactDetails.js +7 -0
  55. package/out-tsc/src/live/ContactDetails.js.map +1 -1
  56. package/out-tsc/src/live/ContactNameFetch.js +1 -1
  57. package/out-tsc/src/live/ContactNameFetch.js.map +1 -1
  58. package/out-tsc/src/webchat/index.js +0 -11
  59. package/out-tsc/src/webchat/index.js.map +1 -1
  60. package/out-tsc/test/NodeHelper.js +25 -27
  61. package/out-tsc/test/NodeHelper.js.map +1 -1
  62. package/out-tsc/test/nodes/split_by_llm.test.js +12 -4
  63. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -1
  64. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +101 -91
  65. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -1
  66. package/out-tsc/test/nodes/split_by_random.test.js +120 -112
  67. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  68. package/out-tsc/test/nodes/wait_for_digits.test.js +131 -111
  69. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  70. package/out-tsc/test/nodes/wait_for_response.test.js +549 -85
  71. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  72. package/out-tsc/test/temba-checkbox.test.js +32 -32
  73. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  74. package/out-tsc/test/temba-contact-chat.test.js +2 -1
  75. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  76. package/out-tsc/test/temba-dropdown.test.js +0 -4
  77. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  78. package/out-tsc/test/temba-flow-editor-node.test.js +9 -4
  79. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  80. package/out-tsc/test/temba-integration-markdown.test.js +13 -15
  81. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  82. package/out-tsc/test/temba-node-editor.test.js +5 -38
  83. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  84. package/out-tsc/test/temba-run-list.test.js +2 -2
  85. package/out-tsc/test/temba-run-list.test.js.map +1 -1
  86. package/out-tsc/test/utils.test.js +2 -1
  87. package/out-tsc/test/utils.test.js.map +1 -1
  88. package/package.json +6 -2
  89. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  90. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  91. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  92. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  93. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  94. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  95. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  96. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  97. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  98. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  99. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  100. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  101. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  102. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  103. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  104. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  105. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  106. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  107. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  108. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  109. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  110. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  111. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  112. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  113. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  114. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  115. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  116. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  117. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  118. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  119. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  120. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  121. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  122. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  123. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  124. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  125. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  126. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  127. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  128. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  129. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  130. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  135. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  136. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  137. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  138. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  139. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  140. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  141. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  142. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  143. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  144. package/screenshots/truth/checkbox/checked.png +0 -0
  145. package/screenshots/truth/checkbox/default.png +0 -0
  146. package/screenshots/truth/editor/wait.png +0 -0
  147. package/screenshots/truth/integration/textinput-markdown-errors.png +0 -0
  148. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  149. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  150. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  151. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  152. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  153. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  154. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  155. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  156. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  157. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  158. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  159. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  160. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  161. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  162. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  163. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  164. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  165. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  166. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  167. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  168. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  169. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  170. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  171. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  172. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  173. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  174. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  175. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  176. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  177. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  178. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  179. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  180. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  181. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  182. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  183. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  184. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  185. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  191. package/screenshots/truth/run-list/basic.png +0 -0
  192. package/screenshots/truth/templates/default.png +0 -0
  193. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  194. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  195. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  196. package/scripts/dev-data-sync.mjs +182 -0
  197. package/src/display/Chat.ts +6 -4
  198. package/src/events.ts +6 -6
  199. package/src/flow/CanvasNode.ts +89 -79
  200. package/src/flow/Editor.ts +1 -0
  201. package/src/flow/NodeEditor.ts +55 -3
  202. package/src/flow/actions/add_contact_urn.ts +1 -1
  203. package/src/flow/actions/set_contact_channel.ts +1 -1
  204. package/src/flow/actions/set_contact_field.ts +2 -1
  205. package/src/flow/actions/set_contact_language.ts +3 -1
  206. package/src/flow/actions/set_contact_name.ts +1 -1
  207. package/src/flow/actions/set_contact_status.ts +18 -18
  208. package/src/flow/actions/set_run_result.ts +1 -1
  209. package/src/flow/nodes/split_by_llm.ts +14 -13
  210. package/src/flow/nodes/wait_for_response.ts +717 -5
  211. package/src/flow/operators.ts +215 -0
  212. package/src/flow/types.ts +10 -2
  213. package/src/form/ArrayEditor.ts +117 -37
  214. package/src/form/Checkbox.ts +12 -0
  215. package/src/form/FieldRenderer.ts +24 -3
  216. package/src/form/TextInput.ts +19 -1
  217. package/src/form/select/Select.ts +7 -0
  218. package/src/interfaces.ts +1 -1
  219. package/src/layout/Dialog.ts +4 -4
  220. package/src/list/RunList.ts +2 -2
  221. package/src/live/ContactChat.ts +128 -67
  222. package/src/live/ContactDetails.ts +7 -0
  223. package/src/live/ContactNameFetch.ts +1 -1
  224. package/src/webchat/index.ts +0 -16
  225. package/static/api/labels.json +6 -1
  226. package/test/NodeHelper.ts +38 -40
  227. package/test/nodes/split_by_llm.test.ts +43 -32
  228. package/test/nodes/split_by_llm_categorize.test.ts +130 -120
  229. package/test/nodes/split_by_random.test.ts +136 -128
  230. package/test/nodes/wait_for_digits.test.ts +147 -127
  231. package/test/nodes/wait_for_response.test.ts +657 -104
  232. package/test/temba-checkbox.test.ts +36 -32
  233. package/test/temba-contact-chat.test.ts +2 -1
  234. package/test/temba-dropdown.test.ts +0 -12
  235. package/test/temba-flow-editor-node.test.ts +11 -4
  236. package/test/temba-integration-markdown.test.ts +16 -17
  237. package/test/temba-node-editor.test.ts +5 -43
  238. package/test/temba-run-list.test.ts +2 -2
  239. package/test/utils.test.ts +2 -1
  240. package/test-assets/contacts/history.json +4 -7
  241. package/test-assets/list/runs.json +8 -8
  242. package/web-dev-mock.mjs +86 -30
  243. package/web-dev-server.config.mjs +272 -31
  244. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  245. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  246. package/screenshots/truth/editor/send_msg.png +0 -0
  247. package/screenshots/truth/editor/set_contact_language.png +0 -0
  248. package/screenshots/truth/editor/set_contact_name.png +0 -0
  249. package/screenshots/truth/editor/set_run_result.png +0 -0
  250. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
@@ -1,6 +1,7 @@
1
1
  import { expect } from '@open-wc/testing';
2
2
  import { wait_for_response } from '../../src/flow/nodes/wait_for_response';
3
3
  import { NodeTest } from '../NodeHelper';
4
+ import { createOperatorOption } from '../../src/flow/operators';
4
5
  /**
5
6
  * Test suite for the wait_for_response node configuration.
6
7
  */
@@ -20,96 +21,101 @@ describe('wait_for_response node config', () => {
20
21
  });
21
22
  it('has layout configuration', () => {
22
23
  expect(wait_for_response.layout).to.exist;
23
- expect(wait_for_response.layout).to.deep.equal([
24
- 'timeout',
25
- 'result_name'
26
- ]);
24
+ expect(wait_for_response.layout).to.deep.equal(['rules', 'result_name']);
27
25
  });
28
26
  });
29
27
  describe('node scenarios', () => {
30
- helper.testNode({
31
- uuid: 'test-wait-node-1',
32
- actions: [],
33
- router: {
34
- type: 'switch',
35
- wait: {
36
- type: 'msg',
37
- timeout: {
38
- category_uuid: 'timeout-cat-1',
39
- seconds: 300
40
- }
28
+ it('renders basic wait', async () => {
29
+ await helper.testNode({
30
+ uuid: 'test-wait-node-1',
31
+ actions: [],
32
+ router: {
33
+ type: 'switch',
34
+ wait: {
35
+ type: 'msg',
36
+ timeout: {
37
+ category_uuid: 'timeout-cat-1',
38
+ seconds: 300
39
+ }
40
+ },
41
+ result_name: 'response',
42
+ categories: [
43
+ {
44
+ uuid: 'timeout-cat-1',
45
+ name: 'No Response',
46
+ exit_uuid: 'timeout-exit-1'
47
+ }
48
+ ]
41
49
  },
42
- result_name: 'response',
43
- categories: [
44
- {
45
- uuid: 'timeout-cat-1',
46
- name: 'No Response',
47
- exit_uuid: 'timeout-exit-1'
48
- }
49
- ]
50
- },
51
- exits: [{ uuid: 'timeout-exit-1', destination_uuid: null }]
52
- }, { type: 'wait_for_response' }, 'basic-wait');
53
- helper.testNode({
54
- uuid: 'test-wait-node-2',
55
- actions: [],
56
- router: {
57
- type: 'switch',
58
- wait: {
59
- type: 'msg',
60
- timeout: {
61
- category_uuid: 'timeout-cat-2',
62
- seconds: 1800
63
- }
50
+ exits: [{ uuid: 'timeout-exit-1', destination_uuid: null }]
51
+ }, { type: 'wait_for_response' }, 'basic-wait');
52
+ });
53
+ it('renders custom result name', async () => {
54
+ await helper.testNode({
55
+ uuid: 'test-wait-node-2',
56
+ actions: [],
57
+ router: {
58
+ type: 'switch',
59
+ wait: {
60
+ type: 'msg',
61
+ timeout: {
62
+ category_uuid: 'timeout-cat-2',
63
+ seconds: 1800
64
+ }
65
+ },
66
+ result_name: 'user_input',
67
+ categories: [
68
+ {
69
+ uuid: 'timeout-cat-2',
70
+ name: 'No Response',
71
+ exit_uuid: 'timeout-exit-2'
72
+ }
73
+ ]
64
74
  },
65
- result_name: 'user_input',
66
- categories: [
67
- {
68
- uuid: 'timeout-cat-2',
69
- name: 'No Response',
70
- exit_uuid: 'timeout-exit-2'
71
- }
72
- ]
73
- },
74
- exits: [{ uuid: 'timeout-exit-2', destination_uuid: null }]
75
- }, { type: 'wait_for_response' }, 'custom-result-name');
76
- helper.testNode({
77
- uuid: 'test-wait-node-3',
78
- actions: [],
79
- router: {
80
- type: 'switch',
81
- wait: {
82
- type: 'msg',
83
- timeout: {
84
- category_uuid: 'timeout-cat-3',
85
- seconds: 60
86
- }
75
+ exits: [{ uuid: 'timeout-exit-2', destination_uuid: null }]
76
+ }, { type: 'wait_for_response' }, 'custom-result-name');
77
+ });
78
+ it('renders short timeout', async () => {
79
+ await helper.testNode({
80
+ uuid: 'test-wait-node-3',
81
+ actions: [],
82
+ router: {
83
+ type: 'switch',
84
+ wait: {
85
+ type: 'msg',
86
+ timeout: {
87
+ category_uuid: 'timeout-cat-3',
88
+ seconds: 60
89
+ }
90
+ },
91
+ result_name: 'quick_response',
92
+ categories: [
93
+ {
94
+ uuid: 'timeout-cat-3',
95
+ name: 'No Response',
96
+ exit_uuid: 'timeout-exit-3'
97
+ }
98
+ ]
87
99
  },
88
- result_name: 'quick_response',
89
- categories: [
90
- {
91
- uuid: 'timeout-cat-3',
92
- name: 'No Response',
93
- exit_uuid: 'timeout-exit-3'
94
- }
95
- ]
96
- },
97
- exits: [{ uuid: 'timeout-exit-3', destination_uuid: null }]
98
- }, { type: 'wait_for_response' }, 'short-timeout');
99
- helper.testNode({
100
- uuid: 'test-wait-node-4',
101
- actions: [],
102
- router: {
103
- type: 'switch',
104
- wait: {
105
- type: 'msg'
106
- // No timeout specified
100
+ exits: [{ uuid: 'timeout-exit-3', destination_uuid: null }]
101
+ }, { type: 'wait_for_response' }, 'short-timeout');
102
+ });
103
+ it('renders no timeout', async () => {
104
+ await helper.testNode({
105
+ uuid: 'test-wait-node-4',
106
+ actions: [],
107
+ router: {
108
+ type: 'switch',
109
+ wait: {
110
+ type: 'msg'
111
+ // No timeout specified
112
+ },
113
+ result_name: 'response',
114
+ categories: []
107
115
  },
108
- result_name: 'response',
109
- categories: []
110
- },
111
- exits: []
112
- }, { type: 'wait_for_response' }, 'no-timeout');
116
+ exits: []
117
+ }, { type: 'wait_for_response' }, 'no-timeout');
118
+ });
113
119
  });
114
120
  describe('data transformation', () => {
115
121
  it('converts node to form data correctly', () => {
@@ -126,12 +132,77 @@ describe('wait_for_response node config', () => {
126
132
  const formData = wait_for_response.toFormData(node);
127
133
  expect(formData.uuid).to.equal('test-node');
128
134
  expect(formData.result_name).to.equal('user_response');
135
+ expect(formData.rules).to.deep.equal([]);
136
+ });
137
+ it('converts node with rules to form data correctly', () => {
138
+ const node = {
139
+ uuid: 'test-node',
140
+ actions: [],
141
+ router: {
142
+ type: 'switch',
143
+ result_name: 'user_response',
144
+ operand: '@input.text',
145
+ categories: [
146
+ {
147
+ uuid: 'red-cat',
148
+ name: 'Red',
149
+ exit_uuid: 'red-exit'
150
+ },
151
+ {
152
+ uuid: 'green-cat',
153
+ name: 'Green',
154
+ exit_uuid: 'green-exit'
155
+ },
156
+ {
157
+ uuid: 'other-cat',
158
+ name: 'Other',
159
+ exit_uuid: 'other-exit'
160
+ }
161
+ ],
162
+ cases: [
163
+ {
164
+ uuid: 'red-case',
165
+ type: 'has_any_word',
166
+ arguments: ['red'],
167
+ category_uuid: 'red-cat'
168
+ },
169
+ {
170
+ uuid: 'green-case',
171
+ type: 'has_phrase',
172
+ arguments: ['green'],
173
+ category_uuid: 'green-cat'
174
+ }
175
+ ]
176
+ },
177
+ exits: [
178
+ { uuid: 'red-exit', destination_uuid: null },
179
+ { uuid: 'green-exit', destination_uuid: null },
180
+ { uuid: 'other-exit', destination_uuid: null }
181
+ ]
182
+ };
183
+ const formData = wait_for_response.toFormData(node);
184
+ expect(formData.uuid).to.equal('test-node');
185
+ expect(formData.result_name).to.equal('user_response');
186
+ expect(formData.rules).to.have.length(2);
187
+ expect(formData.rules[0]).to.deep.equal({
188
+ operator: createOperatorOption('has_any_word'),
189
+ value1: 'red',
190
+ value2: '',
191
+ category: 'Red'
192
+ });
193
+ expect(formData.rules[1]).to.deep.equal({
194
+ operator: createOperatorOption('has_phrase'),
195
+ value1: 'green',
196
+ value2: '',
197
+ category: 'Green'
198
+ });
129
199
  });
130
200
  it('converts form data to node correctly', () => {
131
201
  var _a;
132
202
  const formData = {
133
203
  uuid: 'test-node',
134
- result_name: 'custom_response'
204
+ result_name: 'custom_response',
205
+ rules: []
135
206
  };
136
207
  const originalNode = {
137
208
  uuid: 'test-node',
@@ -147,10 +218,59 @@ describe('wait_for_response node config', () => {
147
218
  expect(result.uuid).to.equal('test-node');
148
219
  expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.result_name).to.equal('custom_response');
149
220
  });
221
+ it('converts form data with rules to node correctly', () => {
222
+ var _a, _b, _c, _d;
223
+ const formData = {
224
+ uuid: 'test-node',
225
+ result_name: 'custom_response',
226
+ rules: [
227
+ {
228
+ operator: 'has_any_word',
229
+ value1: 'red',
230
+ value2: '',
231
+ category: 'Red'
232
+ },
233
+ {
234
+ operator: 'has_phrase',
235
+ value1: 'blue',
236
+ value2: '',
237
+ category: 'Blue'
238
+ }
239
+ ]
240
+ };
241
+ const originalNode = {
242
+ uuid: 'test-node',
243
+ actions: [],
244
+ exits: [],
245
+ router: {
246
+ type: 'switch',
247
+ result_name: 'response',
248
+ categories: []
249
+ }
250
+ };
251
+ const result = wait_for_response.fromFormData(formData, originalNode);
252
+ expect(result.uuid).to.equal('test-node');
253
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.result_name).to.equal('custom_response');
254
+ expect((_b = result.router) === null || _b === void 0 ? void 0 : _b.operand).to.equal('@input.text');
255
+ expect((_c = result.router) === null || _c === void 0 ? void 0 : _c.categories).to.have.length(3); // Red, Blue, Other
256
+ expect((_d = result.router) === null || _d === void 0 ? void 0 : _d.cases).to.have.length(2);
257
+ expect(result.exits).to.have.length(3);
258
+ // Check categories
259
+ const categoryNames = result.router.categories.map((cat) => cat.name);
260
+ expect(categoryNames).to.include.members(['Red', 'Blue', 'Other']);
261
+ // Check cases
262
+ const redCase = result.router.cases.find((c) => c.type === 'has_any_word');
263
+ expect(redCase).to.exist;
264
+ expect(redCase.arguments).to.deep.equal(['red']);
265
+ const blueCase = result.router.cases.find((c) => c.type === 'has_phrase');
266
+ expect(blueCase).to.exist;
267
+ expect(blueCase.arguments).to.deep.equal(['blue']);
268
+ });
150
269
  it('handles default result name', () => {
151
270
  var _a;
152
271
  const formData = {
153
- uuid: 'test-node'
272
+ uuid: 'test-node',
273
+ rules: []
154
274
  // No result_name specified
155
275
  };
156
276
  const originalNode = {
@@ -166,6 +286,350 @@ describe('wait_for_response node config', () => {
166
286
  expect(result.uuid).to.equal('test-node');
167
287
  expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.result_name).to.equal('response');
168
288
  });
289
+ it('handles operators with no operands correctly', () => {
290
+ var _a, _b, _c;
291
+ const formData = {
292
+ uuid: 'test-node',
293
+ result_name: 'custom_response',
294
+ rules: [
295
+ {
296
+ operator: 'has_text',
297
+ value: '', // No value needed for has_text
298
+ category: 'Has Text'
299
+ },
300
+ {
301
+ operator: 'has_number',
302
+ value: '', // No value needed for has_number
303
+ category: 'Has Number'
304
+ }
305
+ ]
306
+ };
307
+ const originalNode = {
308
+ uuid: 'test-node',
309
+ actions: [],
310
+ exits: [],
311
+ router: {
312
+ type: 'switch',
313
+ result_name: 'response',
314
+ categories: []
315
+ }
316
+ };
317
+ const result = wait_for_response.fromFormData(formData, originalNode);
318
+ expect(result.uuid).to.equal('test-node');
319
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.result_name).to.equal('custom_response');
320
+ expect((_b = result.router) === null || _b === void 0 ? void 0 : _b.categories).to.have.length(3); // Has Text, Has Number, Other
321
+ expect((_c = result.router) === null || _c === void 0 ? void 0 : _c.cases).to.have.length(2);
322
+ expect(result.exits).to.have.length(3);
323
+ // Check categories
324
+ const categoryNames = result.router.categories.map((cat) => cat.name);
325
+ expect(categoryNames).to.include.members([
326
+ 'Has Text',
327
+ 'Has Number',
328
+ 'Other'
329
+ ]);
330
+ // Check cases - should have empty arguments for 0-operand operators
331
+ const hasTextCase = result.router.cases.find((c) => c.type === 'has_text');
332
+ expect(hasTextCase).to.exist;
333
+ expect(hasTextCase.arguments).to.deep.equal([]);
334
+ const hasNumberCase = result.router.cases.find((c) => c.type === 'has_number');
335
+ expect(hasNumberCase).to.exist;
336
+ expect(hasNumberCase.arguments).to.deep.equal([]);
337
+ });
338
+ it('preserves timeout categories when adding rules', () => {
339
+ var _a, _b, _c, _d, _e;
340
+ const formData = {
341
+ uuid: 'test-node',
342
+ result_name: 'response',
343
+ timeout_enabled: true,
344
+ timeout_duration: [{ value: '300', name: '5 minutes' }],
345
+ rules: [
346
+ {
347
+ operator: 'has_text',
348
+ value: 'yes',
349
+ category: 'Positive'
350
+ }
351
+ ]
352
+ };
353
+ const originalNode = {
354
+ uuid: 'test-node',
355
+ actions: [],
356
+ router: {
357
+ type: 'switch',
358
+ wait: {
359
+ type: 'msg',
360
+ timeout: {
361
+ category_uuid: 'timeout-cat',
362
+ seconds: 300
363
+ }
364
+ },
365
+ result_name: 'response',
366
+ categories: [
367
+ {
368
+ uuid: 'timeout-cat',
369
+ name: 'No Response',
370
+ exit_uuid: 'timeout-exit'
371
+ }
372
+ ]
373
+ },
374
+ exits: [{ uuid: 'timeout-exit', destination_uuid: null }]
375
+ };
376
+ const result = wait_for_response.fromFormData(formData, originalNode);
377
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.categories).to.have.length(3); // Positive, No Response, Other
378
+ const categoryNames = result.router.categories.map((cat) => cat.name);
379
+ expect(categoryNames).to.include.members([
380
+ 'Positive',
381
+ 'No Response',
382
+ 'Other'
383
+ ]);
384
+ // Verify timeout configuration is preserved
385
+ expect((_b = result.router) === null || _b === void 0 ? void 0 : _b.wait).to.exist;
386
+ expect((_e = (_d = (_c = result.router) === null || _c === void 0 ? void 0 : _c.wait) === null || _d === void 0 ? void 0 : _d.timeout) === null || _e === void 0 ? void 0 : _e.category_uuid).to.equal('timeout-cat');
387
+ });
388
+ });
389
+ describe('validation', () => {
390
+ it('validates form data correctly with no errors', () => {
391
+ const formData = {
392
+ rules: [
393
+ { operator: 'has_text', value: 'yes', category: 'Positive' },
394
+ { operator: 'has_text', value: 'no', category: 'Negative' }
395
+ ]
396
+ };
397
+ const validation = wait_for_response.validate(formData);
398
+ expect(validation.valid).to.be.true;
399
+ expect(validation.errors).to.be.empty;
400
+ });
401
+ it('allows same category names for multiple rules', () => {
402
+ const formData = {
403
+ rules: [
404
+ { operator: 'has_text', value: 'yes', category: 'Positive' },
405
+ { operator: 'has_text', value: 'ok', category: 'positive' }, // case insensitive same category
406
+ { operator: 'has_text', value: 'no', category: 'Negative' }
407
+ ]
408
+ };
409
+ const validation = wait_for_response.validate(formData);
410
+ expect(validation.valid).to.be.true;
411
+ expect(validation.errors).to.be.empty;
412
+ });
413
+ it('allows rules with operators that need no values', () => {
414
+ const formData = {
415
+ rules: [
416
+ { operator: 'has_text', value: '', category: 'Has Text' },
417
+ { operator: 'has_number', value: '', category: 'Has Number' },
418
+ { operator: 'has_any_word', value: 'hello', category: 'Greeting' }
419
+ ]
420
+ };
421
+ const validation = wait_for_response.validate(formData);
422
+ expect(validation.valid).to.be.true;
423
+ expect(validation.errors).to.be.empty;
424
+ });
425
+ it('ignores empty rules in validation', () => {
426
+ const formData = {
427
+ rules: [
428
+ { operator: 'has_phrase', value: 'yes', category: 'Positive' },
429
+ { operator: '', value: '', category: '' }, // empty rule
430
+ { operator: 'has_phrase', value: 'no', category: 'Negative' }
431
+ ]
432
+ };
433
+ const validation = wait_for_response.validate(formData);
434
+ expect(validation.valid).to.be.true;
435
+ expect(validation.errors).to.be.empty;
436
+ });
437
+ it('preserves category UUIDs when names are updated', () => {
438
+ var _a;
439
+ // Create original node with specific UUIDs
440
+ const originalNode = {
441
+ uuid: 'test-node',
442
+ actions: [],
443
+ router: {
444
+ type: 'switch',
445
+ result_name: 'response',
446
+ categories: [
447
+ {
448
+ uuid: 'category-1-uuid',
449
+ name: 'Old Name 1',
450
+ exit_uuid: 'exit-1-uuid'
451
+ },
452
+ {
453
+ uuid: 'category-2-uuid',
454
+ name: 'Old Name 2',
455
+ exit_uuid: 'exit-2-uuid'
456
+ },
457
+ {
458
+ uuid: 'other-category-uuid',
459
+ name: 'Other',
460
+ exit_uuid: 'other-exit-uuid'
461
+ }
462
+ ],
463
+ cases: [
464
+ {
465
+ uuid: 'case-1-uuid',
466
+ type: 'has_phrase',
467
+ arguments: ['yes'],
468
+ category_uuid: 'category-1-uuid'
469
+ },
470
+ {
471
+ uuid: 'case-2-uuid',
472
+ type: 'has_phrase',
473
+ arguments: ['no'],
474
+ category_uuid: 'category-2-uuid'
475
+ }
476
+ ]
477
+ },
478
+ exits: [
479
+ { uuid: 'exit-1-uuid', destination_uuid: null },
480
+ { uuid: 'exit-2-uuid', destination_uuid: null },
481
+ { uuid: 'other-exit-uuid', destination_uuid: null }
482
+ ]
483
+ };
484
+ // Update category names but keep same rules in same order
485
+ const formData = {
486
+ uuid: 'test-node',
487
+ result_name: 'response',
488
+ rules: [
489
+ {
490
+ operator: { value: 'has_phrase', name: 'contains phrase' },
491
+ value1: 'yes',
492
+ category: 'New Name 1' // Changed from "Old Name 1"
493
+ },
494
+ {
495
+ operator: { value: 'has_phrase', name: 'contains phrase' },
496
+ value1: 'no',
497
+ category: 'New Name 2' // Changed from "Old Name 2"
498
+ }
499
+ ]
500
+ };
501
+ const result = wait_for_response.fromFormData(formData, originalNode);
502
+ // Verify that UUIDs are preserved despite name changes
503
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.categories).to.have.length(3); // Two rules + Other
504
+ const category1 = result.router.categories.find((cat) => cat.name === 'New Name 1');
505
+ const category2 = result.router.categories.find((cat) => cat.name === 'New Name 2');
506
+ const otherCategory = result.router.categories.find((cat) => cat.name === 'Other');
507
+ // Verify UUIDs are preserved
508
+ expect(category1 === null || category1 === void 0 ? void 0 : category1.uuid).to.equal('category-1-uuid');
509
+ expect(category1 === null || category1 === void 0 ? void 0 : category1.exit_uuid).to.equal('exit-1-uuid');
510
+ expect(category2 === null || category2 === void 0 ? void 0 : category2.uuid).to.equal('category-2-uuid');
511
+ expect(category2 === null || category2 === void 0 ? void 0 : category2.exit_uuid).to.equal('exit-2-uuid');
512
+ expect(otherCategory === null || otherCategory === void 0 ? void 0 : otherCategory.uuid).to.equal('other-category-uuid');
513
+ expect(otherCategory === null || otherCategory === void 0 ? void 0 : otherCategory.exit_uuid).to.equal('other-exit-uuid');
514
+ // Verify case UUIDs are also preserved
515
+ const case1 = result.router.cases.find((c) => c.category_uuid === 'category-1-uuid');
516
+ const case2 = result.router.cases.find((c) => c.category_uuid === 'category-2-uuid');
517
+ expect(case1 === null || case1 === void 0 ? void 0 : case1.uuid).to.equal('case-1-uuid');
518
+ expect(case2 === null || case2 === void 0 ? void 0 : case2.uuid).to.equal('case-2-uuid');
519
+ // Verify exits are preserved
520
+ expect(result.exits).to.have.length(3);
521
+ expect(result.exits.map((e) => e.uuid)).to.include.members([
522
+ 'exit-1-uuid',
523
+ 'exit-2-uuid',
524
+ 'other-exit-uuid'
525
+ ]);
526
+ });
527
+ it('merges rules with same category name into single category', () => {
528
+ var _a, _b;
529
+ const formData = {
530
+ uuid: 'test-node',
531
+ result_name: 'response',
532
+ rules: [
533
+ {
534
+ operator: { value: 'has_phrase', name: 'contains phrase' },
535
+ value1: 'yes',
536
+ category: 'Positive'
537
+ },
538
+ {
539
+ operator: { value: 'has_phrase', name: 'contains phrase' },
540
+ value1: 'ok',
541
+ category: 'Positive' // Same category name
542
+ },
543
+ {
544
+ operator: { value: 'has_phrase', name: 'contains phrase' },
545
+ value1: 'no',
546
+ category: 'Negative'
547
+ }
548
+ ]
549
+ };
550
+ const originalNode = {
551
+ uuid: 'test-node',
552
+ actions: [],
553
+ router: {
554
+ type: 'switch',
555
+ result_name: 'response',
556
+ categories: [],
557
+ cases: []
558
+ },
559
+ exits: []
560
+ };
561
+ const result = wait_for_response.fromFormData(formData, originalNode);
562
+ // Should have 3 categories: Positive, Negative, Other
563
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.categories).to.have.length(3);
564
+ const categoryNames = result.router.categories.map((cat) => cat.name);
565
+ expect(categoryNames).to.include.members([
566
+ 'Positive',
567
+ 'Negative',
568
+ 'Other'
569
+ ]);
570
+ // Should have 3 cases but only 2 user categories (+ Other)
571
+ expect((_b = result.router) === null || _b === void 0 ? void 0 : _b.cases).to.have.length(3);
572
+ // Both "yes" and "ok" rules should reference the same Positive category
573
+ const positiveCategory = result.router.categories.find((cat) => cat.name === 'Positive');
574
+ expect(positiveCategory).to.exist;
575
+ const positiveCases = result.router.cases.filter((case_) => case_.category_uuid === positiveCategory.uuid);
576
+ expect(positiveCases).to.have.length(2);
577
+ // Verify the cases have the correct arguments
578
+ const yesCase = positiveCases.find((case_) => case_.arguments.includes('yes'));
579
+ const okCase = positiveCases.find((case_) => case_.arguments.includes('ok'));
580
+ expect(yesCase).to.exist;
581
+ expect(okCase).to.exist;
582
+ // Should have 3 exits: Positive, Negative, Other
583
+ expect(result.exits).to.have.length(3);
584
+ });
585
+ it('preserves category order when merging same category names', () => {
586
+ var _a;
587
+ const formData = {
588
+ uuid: 'test-node',
589
+ result_name: 'response',
590
+ rules: [
591
+ {
592
+ operator: { value: 'has_phrase', name: 'contains phrase' },
593
+ value1: 'yes',
594
+ category: 'First'
595
+ },
596
+ {
597
+ operator: { value: 'has_phrase', name: 'contains phrase' },
598
+ value1: 'maybe',
599
+ category: 'Second'
600
+ },
601
+ {
602
+ operator: { value: 'has_phrase', name: 'contains phrase' },
603
+ value1: 'ok',
604
+ category: 'First' // Same as first rule
605
+ }
606
+ ]
607
+ };
608
+ const originalNode = {
609
+ uuid: 'test-node',
610
+ actions: [],
611
+ router: {
612
+ type: 'switch',
613
+ result_name: 'response',
614
+ categories: [],
615
+ cases: []
616
+ },
617
+ exits: []
618
+ };
619
+ const result = wait_for_response.fromFormData(formData, originalNode);
620
+ // Should have 3 categories: First, Second, Other (in that order)
621
+ expect((_a = result.router) === null || _a === void 0 ? void 0 : _a.categories).to.have.length(3);
622
+ const categoryNames = result.router.categories.map((cat) => cat.name);
623
+ expect(categoryNames).to.deep.equal(['First', 'Second', 'Other']);
624
+ // First category should have 2 cases (yes and ok)
625
+ const firstCategory = result.router.categories.find((cat) => cat.name === 'First');
626
+ const firstCases = result.router.cases.filter((case_) => case_.category_uuid === firstCategory.uuid);
627
+ expect(firstCases).to.have.length(2);
628
+ // Second category should have 1 case (maybe)
629
+ const secondCategory = result.router.categories.find((cat) => cat.name === 'Second');
630
+ const secondCases = result.router.cases.filter((case_) => case_.category_uuid === secondCategory.uuid);
631
+ expect(secondCases).to.have.length(1);
632
+ });
169
633
  });
170
634
  });
171
635
  //# sourceMappingURL=wait_for_response.test.js.map