@nyaruka/temba-components 0.129.7 → 0.129.9

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 (269) hide show
  1. package/.devcontainer/Dockerfile +11 -4
  2. package/.devcontainer/devcontainer.json +3 -2
  3. package/.github/workflows/build.yml +4 -14
  4. package/CHANGELOG.md +29 -0
  5. package/demo/components/flow/example.html +1 -1
  6. package/demo/components/message-editor/example.html +125 -0
  7. package/demo/components/textinput/completion.html +1 -0
  8. package/demo/data/flows/food-order.json +12 -21
  9. package/demo/data/flows/sample-flow.json +210 -104
  10. package/dist/temba-components.js +715 -364
  11. package/dist/temba-components.js.map +1 -1
  12. package/out-tsc/src/display/Thumbnail.js +2 -1
  13. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  14. package/out-tsc/src/events.js.map +1 -1
  15. package/out-tsc/src/excellent/helpers.js +2 -2
  16. package/out-tsc/src/excellent/helpers.js.map +1 -1
  17. package/out-tsc/src/flow/CanvasNode.js +25 -7
  18. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  19. package/out-tsc/src/flow/Editor.js +11 -1
  20. package/out-tsc/src/flow/Editor.js.map +1 -1
  21. package/out-tsc/src/flow/NodeEditor.js +342 -276
  22. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
  24. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  25. package/out-tsc/src/flow/actions/call_llm.js +56 -3
  26. package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
  27. package/out-tsc/src/flow/actions/call_webhook.js +26 -17
  28. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
  29. package/out-tsc/src/flow/actions/open_ticket.js +65 -3
  30. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
  31. package/out-tsc/src/flow/actions/send_msg.js +147 -6
  32. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  33. package/out-tsc/src/flow/actions/set_run_result.js +75 -0
  34. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  35. package/out-tsc/src/flow/config.js +4 -0
  36. package/out-tsc/src/flow/config.js.map +1 -1
  37. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
  38. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
  39. package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
  40. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
  41. package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
  42. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  43. package/out-tsc/src/flow/types.js +0 -65
  44. package/out-tsc/src/flow/types.js.map +1 -1
  45. package/out-tsc/src/form/ArrayEditor.js +87 -57
  46. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  47. package/out-tsc/src/form/BaseListEditor.js +19 -4
  48. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  49. package/out-tsc/src/form/FieldRenderer.js +305 -0
  50. package/out-tsc/src/form/FieldRenderer.js.map +1 -0
  51. package/out-tsc/src/form/FormField.js +4 -4
  52. package/out-tsc/src/form/FormField.js.map +1 -1
  53. package/out-tsc/src/form/KeyValueEditor.js +1 -1
  54. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  55. package/out-tsc/src/form/MediaPicker.js +13 -1
  56. package/out-tsc/src/form/MediaPicker.js.map +1 -1
  57. package/out-tsc/src/form/MessageEditor.js +422 -0
  58. package/out-tsc/src/form/MessageEditor.js.map +1 -0
  59. package/out-tsc/src/form/TextInput.js +13 -6
  60. package/out-tsc/src/form/TextInput.js.map +1 -1
  61. package/out-tsc/src/form/select/Select.js +52 -24
  62. package/out-tsc/src/form/select/Select.js.map +1 -1
  63. package/out-tsc/src/live/ContactChat.js +66 -15
  64. package/out-tsc/src/live/ContactChat.js.map +1 -1
  65. package/out-tsc/src/markdown.js +13 -11
  66. package/out-tsc/src/markdown.js.map +1 -1
  67. package/out-tsc/temba-modules.js +2 -0
  68. package/out-tsc/temba-modules.js.map +1 -1
  69. package/out-tsc/test/ActionHelper.js +2 -0
  70. package/out-tsc/test/ActionHelper.js.map +1 -1
  71. package/out-tsc/test/NodeHelper.js +148 -0
  72. package/out-tsc/test/NodeHelper.js.map +1 -0
  73. package/out-tsc/test/actions/call_llm.test.js +103 -0
  74. package/out-tsc/test/actions/call_llm.test.js.map +1 -0
  75. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
  76. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
  77. package/out-tsc/test/nodes/split_by_random.test.js +150 -0
  78. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
  79. package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
  80. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
  81. package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
  82. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
  83. package/out-tsc/test/temba-add-input-labels.test.js +70 -0
  84. package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
  85. package/out-tsc/test/temba-field-config.test.js +4 -2
  86. package/out-tsc/test/temba-field-config.test.js.map +1 -1
  87. package/out-tsc/test/temba-field-renderer.test.js +296 -0
  88. package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
  89. package/out-tsc/test/temba-markdown.test.js +1 -1
  90. package/out-tsc/test/temba-markdown.test.js.map +1 -1
  91. package/out-tsc/test/temba-message-editor.test.js +194 -0
  92. package/out-tsc/test/temba-message-editor.test.js.map +1 -0
  93. package/out-tsc/test/temba-node-editor.test.js +471 -0
  94. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  95. package/out-tsc/test/temba-select.test.js +7 -4
  96. package/out-tsc/test/temba-select.test.js.map +1 -1
  97. package/out-tsc/test/temba-textinput.test.js +16 -0
  98. package/out-tsc/test/temba-textinput.test.js.map +1 -1
  99. package/out-tsc/test/temba-webchat.test.js +5 -1
  100. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  101. package/out-tsc/test/utils.test.js +2 -8
  102. package/out-tsc/test/utils.test.js.map +1 -1
  103. package/package.json +7 -4
  104. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  105. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  106. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  107. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  108. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  109. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  110. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  111. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  112. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  113. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  114. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  115. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  116. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  117. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  118. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  119. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  120. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  121. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  122. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  123. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  124. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  125. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  126. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  127. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  128. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  129. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  130. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  134. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  135. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  136. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  137. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  138. package/screenshots/truth/editor/router.png +0 -0
  139. package/screenshots/truth/editor/send_msg.png +0 -0
  140. package/screenshots/truth/editor/set_contact_language.png +0 -0
  141. package/screenshots/truth/editor/set_contact_name.png +0 -0
  142. package/screenshots/truth/editor/set_run_result.png +0 -0
  143. package/screenshots/truth/editor/wait.png +0 -0
  144. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  145. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  146. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  147. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  148. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  149. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  150. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  151. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  152. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  153. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  154. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  155. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  156. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  157. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  158. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  159. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  160. package/screenshots/truth/formfield/no-errors.png +0 -0
  161. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  162. package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
  163. package/screenshots/truth/message-editor/default.png +0 -0
  164. package/screenshots/truth/message-editor/drag-highlight.png +0 -0
  165. package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
  166. package/screenshots/truth/message-editor/with-completion.png +0 -0
  167. package/screenshots/truth/message-editor/with-properties.png +0 -0
  168. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  169. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  170. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  171. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  172. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  173. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  174. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  175. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  176. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  177. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  178. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  179. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  180. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  181. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  182. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  183. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  184. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  185. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  191. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  197. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  198. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  199. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  200. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  201. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  202. package/screenshots/truth/omnibox/selected.png +0 -0
  203. package/screenshots/truth/select/functions.png +0 -0
  204. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  205. package/screenshots/truth/select/search-enabled.png +0 -0
  206. package/screenshots/truth/textinput/autogrow-initial.png +0 -0
  207. package/screenshots/truth/textinput/input-form.png +0 -0
  208. package/src/display/Thumbnail.ts +2 -1
  209. package/src/events.ts +13 -1
  210. package/src/excellent/helpers.ts +2 -2
  211. package/src/flow/CanvasNode.ts +22 -1
  212. package/src/flow/Editor.ts +12 -1
  213. package/src/flow/NodeEditor.ts +412 -354
  214. package/src/flow/actions/add_input_labels.ts +45 -0
  215. package/src/flow/actions/call_llm.ts +57 -3
  216. package/src/flow/actions/call_webhook.ts +28 -18
  217. package/src/flow/actions/open_ticket.ts +74 -3
  218. package/src/flow/actions/send_msg.ts +170 -6
  219. package/src/flow/actions/set_run_result.ts +83 -0
  220. package/src/flow/config.ts +4 -0
  221. package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
  222. package/src/flow/nodes/split_by_ticket.ts +19 -0
  223. package/src/flow/nodes/wait_for_response.ts +28 -1
  224. package/src/flow/types.ts +46 -128
  225. package/src/form/ArrayEditor.ts +96 -66
  226. package/src/form/BaseListEditor.ts +22 -6
  227. package/src/form/FieldRenderer.ts +465 -0
  228. package/src/form/FormField.ts +4 -4
  229. package/src/form/KeyValueEditor.ts +1 -1
  230. package/src/form/MediaPicker.ts +13 -1
  231. package/src/form/MessageEditor.ts +449 -0
  232. package/src/form/TextInput.ts +16 -8
  233. package/src/form/select/Select.ts +55 -24
  234. package/src/live/ContactChat.ts +69 -19
  235. package/src/markdown.ts +19 -11
  236. package/src/store/flow-definition.d.ts +5 -2
  237. package/static/api/labels.json +31 -0
  238. package/static/api/topics.json +24 -9
  239. package/static/api/users.json +35 -16
  240. package/static/css/temba-components.css +5 -3
  241. package/static/mr/docs/en-us/editor.json +2588 -0
  242. package/stress-test.js +143 -0
  243. package/temba-modules.ts +2 -0
  244. package/test/ActionHelper.ts +2 -0
  245. package/test/NodeHelper.ts +184 -0
  246. package/test/actions/call_llm.test.ts +137 -0
  247. package/test/nodes/README.md +78 -0
  248. package/test/nodes/split_by_llm_categorize.test.ts +698 -0
  249. package/test/nodes/split_by_random.test.ts +177 -0
  250. package/test/nodes/wait_for_digits.test.ts +176 -0
  251. package/test/nodes/wait_for_response.test.ts +206 -0
  252. package/test/temba-add-input-labels.test.ts +87 -0
  253. package/test/temba-field-config.test.ts +4 -2
  254. package/test/temba-field-renderer.test.ts +482 -0
  255. package/test/temba-markdown.test.ts +1 -1
  256. package/test/temba-message-editor.test.ts +300 -0
  257. package/test/temba-node-editor.test.ts +590 -0
  258. package/test/temba-select.test.ts +7 -7
  259. package/test/temba-textinput.test.ts +26 -0
  260. package/test/temba-webchat.test.ts +6 -1
  261. package/test/utils.test.ts +2 -13
  262. package/test-assets/contacts/history.json +19 -0
  263. package/test-assets/select/llms.json +18 -0
  264. package/test-assets/style.css +2 -0
  265. package/web-dev-mock.mjs +523 -0
  266. package/web-dev-server.config.mjs +74 -6
  267. package/web-test-runner.config.mjs +9 -4
  268. package/test/temba-flow-editor.test.ts.backup +0 -563
  269. package/test/temba-utils-index.test.ts.backup +0 -1737
@@ -0,0 +1,300 @@
1
+ import { fixture, assert, expect } from '@open-wc/testing';
2
+ import { MessageEditor } from '../src/form/MessageEditor';
3
+ import { assertScreenshot, getClip, getComponent } from './utils.test';
4
+
5
+ export const getHTML = (options: any = {}) => {
6
+ const attrs = Object.keys(options)
7
+ .map((key) => `${key}="${options[key]}"`)
8
+ .join(' ');
9
+ return `<temba-message-editor ${attrs}></temba-message-editor>`;
10
+ };
11
+
12
+ describe('temba-message-editor', () => {
13
+ it('can be created', async () => {
14
+ const editor: MessageEditor = await fixture(getHTML());
15
+ assert.instanceOf(editor, MessageEditor);
16
+ });
17
+
18
+ it('has default properties', async () => {
19
+ const editor = (await getComponent(
20
+ 'temba-message-editor'
21
+ )) as MessageEditor;
22
+
23
+ expect(editor.name).to.equal('');
24
+ expect(editor.value).to.equal('');
25
+ expect(editor.placeholder).to.equal('');
26
+ expect(editor.textarea).to.be.true;
27
+ expect(editor.autogrow).to.be.true;
28
+ expect(editor.minHeight).to.equal(60);
29
+ expect(editor.attachments).to.deep.equal([]);
30
+ expect(editor.maxAttachments).to.equal(3);
31
+
32
+ await assertScreenshot(
33
+ 'message-editor/default',
34
+ getClip(editor as HTMLElement)
35
+ );
36
+ });
37
+
38
+ it('can set properties', async () => {
39
+ const editor = (await getComponent('temba-message-editor', {
40
+ name: 'message',
41
+ value: 'Hello world',
42
+ placeholder: 'Type your message...',
43
+ 'max-attachments': '5'
44
+ })) as MessageEditor;
45
+
46
+ expect(editor.name).to.equal('message');
47
+ expect(editor.value).to.equal('Hello world');
48
+ expect(editor.placeholder).to.equal('Type your message...');
49
+ expect(editor.maxAttachments).to.equal(5);
50
+
51
+ await assertScreenshot(
52
+ 'message-editor/with-properties',
53
+ getClip(editor as HTMLElement)
54
+ );
55
+ });
56
+
57
+ it('renders completion component', async () => {
58
+ const editor = (await getComponent('temba-message-editor', {
59
+ value: 'Test message',
60
+ placeholder: 'Enter message'
61
+ })) as MessageEditor;
62
+
63
+ const completion = editor.shadowRoot.querySelector(
64
+ 'temba-completion'
65
+ ) as any;
66
+ expect(completion).to.not.be.null;
67
+ expect(completion.hasAttribute('widgetOnly')).to.be.true;
68
+
69
+ await assertScreenshot(
70
+ 'message-editor/with-completion',
71
+ getClip(editor as HTMLElement)
72
+ );
73
+ });
74
+
75
+ it('filters runtime attachments', async () => {
76
+ const attachments = [
77
+ 'image/jpeg:http://example.com/image.jpg',
78
+ 'image:@fields.profile_pic',
79
+ 'video:@fields.intro_video',
80
+ 'application/pdf:http://example.com/doc.pdf'
81
+ ];
82
+
83
+ const editor = (await getComponent('temba-message-editor', {
84
+ attachments: JSON.stringify(attachments)
85
+ })) as MessageEditor;
86
+
87
+ // Wait for component to update
88
+ await editor.updateComplete;
89
+
90
+ const mediaPicker = editor.shadowRoot.querySelector(
91
+ 'temba-media-picker'
92
+ ) as any;
93
+ expect(mediaPicker).to.not.be.null;
94
+
95
+ // Should only have the static attachments (those with '/' in content type)
96
+ expect(mediaPicker.attachments.length).to.equal(2);
97
+ expect(mediaPicker.attachments[0].content_type).to.equal('image/jpeg');
98
+ expect(mediaPicker.attachments[1].content_type).to.equal('application/pdf');
99
+
100
+ await assertScreenshot(
101
+ 'message-editor/filtered-attachments',
102
+ getClip(editor as HTMLElement)
103
+ );
104
+ });
105
+
106
+ it('handles completion change events', async () => {
107
+ const editor = (await getComponent(
108
+ 'temba-message-editor'
109
+ )) as MessageEditor;
110
+ let changeEvent: CustomEvent = null;
111
+
112
+ editor.addEventListener('change', (e: CustomEvent) => {
113
+ changeEvent = e;
114
+ });
115
+
116
+ const completion = editor.shadowRoot.querySelector(
117
+ 'temba-completion'
118
+ ) as any;
119
+ completion.value = 'New message';
120
+ completion.dispatchEvent(new Event('change'));
121
+
122
+ expect(editor.value).to.equal('New message');
123
+ expect(changeEvent).to.not.be.null;
124
+ });
125
+
126
+ it('handles media picker change events', async () => {
127
+ const editor = (await getComponent(
128
+ 'temba-message-editor'
129
+ )) as MessageEditor;
130
+ let changeEvent: CustomEvent = null;
131
+
132
+ editor.addEventListener('change', (e: CustomEvent) => {
133
+ changeEvent = e;
134
+ });
135
+
136
+ const mediaPicker = editor.shadowRoot.querySelector(
137
+ 'temba-media-picker'
138
+ ) as any;
139
+ mediaPicker.attachments = [
140
+ {
141
+ content_type: 'image/jpeg',
142
+ url: 'http://example.com/test.jpg',
143
+ filename: 'test.jpg',
144
+ size: 1024
145
+ }
146
+ ];
147
+ mediaPicker.dispatchEvent(new Event('change'));
148
+
149
+ expect(editor.attachments).to.include(
150
+ 'image/jpeg:http://example.com/test.jpg'
151
+ );
152
+ expect(changeEvent).to.not.be.null;
153
+ });
154
+
155
+ it('preserves runtime attachments when media changes', async () => {
156
+ const initialAttachments = [
157
+ 'image:@fields.profile_pic',
158
+ 'image/jpeg:http://example.com/existing.jpg'
159
+ ];
160
+
161
+ const editor = (await getComponent('temba-message-editor', {
162
+ attachments: JSON.stringify(initialAttachments)
163
+ })) as MessageEditor;
164
+
165
+ await editor.updateComplete;
166
+
167
+ // Simulate media picker change
168
+ const mediaPicker = editor.shadowRoot.querySelector(
169
+ 'temba-media-picker'
170
+ ) as any;
171
+ mediaPicker.attachments = [
172
+ {
173
+ content_type: 'image/png',
174
+ url: 'http://example.com/new.png',
175
+ filename: 'new.png',
176
+ size: 2048
177
+ }
178
+ ];
179
+ mediaPicker.dispatchEvent(new Event('change'));
180
+
181
+ // Should preserve runtime attachments and add new static ones
182
+ expect(editor.attachments).to.include('image:@fields.profile_pic');
183
+ expect(editor.attachments).to.include(
184
+ 'image/png:http://example.com/new.png'
185
+ );
186
+ expect(editor.attachments.length).to.equal(2);
187
+ });
188
+
189
+ it('supports drag and drop highlighting', async () => {
190
+ const editor = (await getComponent(
191
+ 'temba-message-editor'
192
+ )) as MessageEditor;
193
+ const container = editor.shadowRoot.querySelector(
194
+ '.message-editor-container'
195
+ );
196
+
197
+ // Simulate drag enter
198
+ const dragEvent = new DragEvent('dragenter', {
199
+ bubbles: true,
200
+ dataTransfer: new DataTransfer()
201
+ });
202
+ container.dispatchEvent(dragEvent);
203
+
204
+ // Wait for the update
205
+ await editor.updateComplete;
206
+
207
+ expect(editor.pendingDrop).to.be.true;
208
+ expect(container.classList.contains('highlight')).to.be.true;
209
+
210
+ await assertScreenshot(
211
+ 'message-editor/drag-highlight',
212
+ getClip(editor as HTMLElement)
213
+ );
214
+
215
+ // Simulate drag leave
216
+ const dragLeaveEvent = new DragEvent('dragleave', {
217
+ bubbles: true,
218
+ dataTransfer: new DataTransfer()
219
+ });
220
+ container.dispatchEvent(dragLeaveEvent);
221
+
222
+ expect(editor.pendingDrop).to.be.false;
223
+ });
224
+
225
+ it('focuses completion on focus', async () => {
226
+ const editor = (await getComponent(
227
+ 'temba-message-editor'
228
+ )) as MessageEditor;
229
+ const completion = editor.shadowRoot.querySelector(
230
+ 'temba-completion'
231
+ ) as any;
232
+
233
+ let focusCalled = false;
234
+ completion.focus = () => {
235
+ focusCalled = true;
236
+ };
237
+
238
+ editor.focus();
239
+ expect(focusCalled).to.be.true;
240
+ });
241
+
242
+ it('clicks completion on click', async () => {
243
+ const editor = (await getComponent(
244
+ 'temba-message-editor'
245
+ )) as MessageEditor;
246
+ const completion = editor.shadowRoot.querySelector(
247
+ 'temba-completion'
248
+ ) as any;
249
+
250
+ let clickCalled = false;
251
+ completion.click = () => {
252
+ clickCalled = true;
253
+ };
254
+
255
+ editor.click();
256
+ expect(clickCalled).to.be.true;
257
+ });
258
+
259
+ it('initializes with correct height for long text content', async () => {
260
+ const longText =
261
+ 'This is a very long text that should span multiple lines and cause the autogrow functionality to kick in and expand the textarea to accommodate all the content. This text should be long enough to trigger the autogrow behavior during initialization.';
262
+
263
+ const editor = (await getComponent('temba-message-editor', {
264
+ value: longText,
265
+ 'min-height': '60'
266
+ })) as MessageEditor;
267
+
268
+ // Wait for component to fully render
269
+ await editor.updateComplete;
270
+
271
+ // Get the text input element to verify its height
272
+ const completion = editor.shadowRoot.querySelector(
273
+ 'temba-completion'
274
+ ) as any;
275
+ expect(completion).to.not.be.null;
276
+
277
+ // The completion should have the long text value
278
+ expect(completion.value).to.equal(longText);
279
+
280
+ // Get the actual TextInput component inside the completion
281
+ const textInput = completion.getTextInput();
282
+ expect(textInput).to.not.be.null;
283
+
284
+ // The textarea should be in autogrow mode
285
+ expect(textInput.autogrow).to.be.true;
286
+ expect(textInput.textarea).to.be.true;
287
+
288
+ // Check that the autogrow div has been updated with content
289
+ const autogrowDiv = textInput.shadowRoot.querySelector(
290
+ '.grow-wrap > div'
291
+ ) as HTMLDivElement;
292
+ expect(autogrowDiv).to.not.be.null;
293
+ expect(autogrowDiv.innerText).to.include(longText);
294
+
295
+ await assertScreenshot(
296
+ 'message-editor/autogrow-initial-content',
297
+ getClip(editor as HTMLElement)
298
+ );
299
+ });
300
+ });