@nyaruka/temba-components 0.129.8 → 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 (203) hide show
  1. package/CHANGELOG.md +27 -3
  2. package/demo/data/flows/sample-flow.json +186 -96
  3. package/dist/temba-components.js +414 -351
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/events.js.map +1 -1
  6. package/out-tsc/src/excellent/helpers.js +2 -2
  7. package/out-tsc/src/excellent/helpers.js.map +1 -1
  8. package/out-tsc/src/flow/CanvasNode.js +25 -7
  9. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  10. package/out-tsc/src/flow/Editor.js +11 -1
  11. package/out-tsc/src/flow/Editor.js.map +1 -1
  12. package/out-tsc/src/flow/NodeEditor.js +133 -290
  13. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  14. package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
  15. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  16. package/out-tsc/src/flow/actions/call_llm.js +56 -3
  17. package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
  18. package/out-tsc/src/flow/actions/call_webhook.js +1 -1
  19. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
  20. package/out-tsc/src/flow/actions/open_ticket.js +65 -3
  21. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_run_result.js +75 -0
  23. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  24. package/out-tsc/src/flow/config.js +4 -0
  25. package/out-tsc/src/flow/config.js.map +1 -1
  26. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
  27. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
  28. package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
  29. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
  30. package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
  31. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  32. package/out-tsc/src/flow/types.js +0 -65
  33. package/out-tsc/src/flow/types.js.map +1 -1
  34. package/out-tsc/src/form/ArrayEditor.js +18 -61
  35. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  36. package/out-tsc/src/form/FieldRenderer.js +305 -0
  37. package/out-tsc/src/form/FieldRenderer.js.map +1 -0
  38. package/out-tsc/src/form/FormField.js +3 -3
  39. package/out-tsc/src/form/FormField.js.map +1 -1
  40. package/out-tsc/src/form/TextInput.js +1 -1
  41. package/out-tsc/src/form/TextInput.js.map +1 -1
  42. package/out-tsc/src/form/select/Select.js +48 -20
  43. package/out-tsc/src/form/select/Select.js.map +1 -1
  44. package/out-tsc/src/live/ContactChat.js +39 -13
  45. package/out-tsc/src/live/ContactChat.js.map +1 -1
  46. package/out-tsc/src/markdown.js +13 -11
  47. package/out-tsc/src/markdown.js.map +1 -1
  48. package/out-tsc/test/ActionHelper.js +2 -0
  49. package/out-tsc/test/ActionHelper.js.map +1 -1
  50. package/out-tsc/test/NodeHelper.js +148 -0
  51. package/out-tsc/test/NodeHelper.js.map +1 -0
  52. package/out-tsc/test/actions/call_llm.test.js +103 -0
  53. package/out-tsc/test/actions/call_llm.test.js.map +1 -0
  54. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
  55. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
  56. package/out-tsc/test/nodes/split_by_random.test.js +150 -0
  57. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
  58. package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
  59. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
  60. package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
  61. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
  62. package/out-tsc/test/temba-add-input-labels.test.js +70 -0
  63. package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
  64. package/out-tsc/test/temba-field-renderer.test.js +296 -0
  65. package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
  66. package/out-tsc/test/temba-markdown.test.js +1 -1
  67. package/out-tsc/test/temba-markdown.test.js.map +1 -1
  68. package/out-tsc/test/temba-node-editor.test.js +400 -0
  69. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  70. package/out-tsc/test/temba-select.test.js +6 -3
  71. package/out-tsc/test/temba-select.test.js.map +1 -1
  72. package/out-tsc/test/temba-webchat.test.js +1 -1
  73. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  74. package/package.json +1 -1
  75. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  76. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  77. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  79. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  80. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  81. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  82. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  83. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  84. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  85. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  86. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  87. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  88. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  89. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  90. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  91. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  92. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  93. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  94. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  95. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  96. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  97. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  98. package/screenshots/truth/editor/router.png +0 -0
  99. package/screenshots/truth/editor/send_msg.png +0 -0
  100. package/screenshots/truth/editor/set_contact_language.png +0 -0
  101. package/screenshots/truth/editor/set_contact_name.png +0 -0
  102. package/screenshots/truth/editor/set_run_result.png +0 -0
  103. package/screenshots/truth/editor/wait.png +0 -0
  104. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  105. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  106. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  107. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  108. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  109. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  110. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  111. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  112. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  113. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  114. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  115. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  116. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  117. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  118. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  119. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  120. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  121. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  122. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  123. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  124. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  125. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  126. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  127. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  128. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  129. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  130. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  131. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  132. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  133. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  134. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  135. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  136. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  142. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  143. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  144. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  145. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  146. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  147. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  148. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  149. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  150. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  151. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  152. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  153. package/screenshots/truth/omnibox/selected.png +0 -0
  154. package/screenshots/truth/select/functions.png +0 -0
  155. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  156. package/screenshots/truth/select/search-enabled.png +0 -0
  157. package/src/events.ts +8 -1
  158. package/src/excellent/helpers.ts +2 -2
  159. package/src/flow/CanvasNode.ts +22 -1
  160. package/src/flow/Editor.ts +12 -1
  161. package/src/flow/NodeEditor.ts +186 -374
  162. package/src/flow/actions/add_input_labels.ts +45 -0
  163. package/src/flow/actions/call_llm.ts +57 -3
  164. package/src/flow/actions/call_webhook.ts +1 -1
  165. package/src/flow/actions/open_ticket.ts +74 -3
  166. package/src/flow/actions/set_run_result.ts +83 -0
  167. package/src/flow/config.ts +4 -0
  168. package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
  169. package/src/flow/nodes/split_by_ticket.ts +19 -0
  170. package/src/flow/nodes/wait_for_response.ts +28 -1
  171. package/src/flow/types.ts +26 -127
  172. package/src/form/ArrayEditor.ts +34 -82
  173. package/src/form/FieldRenderer.ts +465 -0
  174. package/src/form/FormField.ts +3 -3
  175. package/src/form/TextInput.ts +1 -1
  176. package/src/form/select/Select.ts +51 -20
  177. package/src/live/ContactChat.ts +39 -15
  178. package/src/markdown.ts +19 -11
  179. package/src/store/flow-definition.d.ts +5 -2
  180. package/static/api/labels.json +31 -0
  181. package/static/api/topics.json +24 -9
  182. package/static/api/users.json +35 -16
  183. package/static/css/temba-components.css +3 -3
  184. package/stress-test.js +18 -13
  185. package/test/ActionHelper.ts +2 -0
  186. package/test/NodeHelper.ts +184 -0
  187. package/test/actions/call_llm.test.ts +137 -0
  188. package/test/nodes/README.md +78 -0
  189. package/test/nodes/split_by_llm_categorize.test.ts +698 -0
  190. package/test/nodes/split_by_random.test.ts +177 -0
  191. package/test/nodes/wait_for_digits.test.ts +176 -0
  192. package/test/nodes/wait_for_response.test.ts +206 -0
  193. package/test/temba-add-input-labels.test.ts +87 -0
  194. package/test/temba-field-renderer.test.ts +482 -0
  195. package/test/temba-markdown.test.ts +1 -1
  196. package/test/temba-node-editor.test.ts +496 -0
  197. package/test/temba-select.test.ts +6 -6
  198. package/test/temba-webchat.test.ts +1 -1
  199. package/test-assets/select/llms.json +18 -0
  200. package/web-dev-mock.mjs +96 -6
  201. package/web-dev-server.config.mjs +29 -7
  202. package/test/temba-flow-editor.test.ts.backup +0 -563
  203. package/test/temba-utils-index.test.ts.backup +0 -1737
@@ -0,0 +1,482 @@
1
+ import { expect, fixture, html } from '@open-wc/testing';
2
+ import { FieldRenderer } from '../src/form/FieldRenderer';
3
+ import {
4
+ TextFieldConfig,
5
+ TextareaFieldConfig,
6
+ SelectFieldConfig,
7
+ CheckboxFieldConfig,
8
+ KeyValueFieldConfig,
9
+ MessageEditorFieldConfig
10
+ } from '../src/flow/types';
11
+ import { assertScreenshot, getClip } from './utils.test';
12
+
13
+ describe('FieldRenderer', () => {
14
+ describe('text fields', () => {
15
+ it('should render text field with label', async () => {
16
+ const config: TextFieldConfig = {
17
+ type: 'text',
18
+ label: 'Website URL',
19
+ placeholder: 'Enter URL'
20
+ };
21
+
22
+ const template = FieldRenderer.renderField(
23
+ 'url',
24
+ config,
25
+ 'https://example.com',
26
+ {
27
+ errors: [],
28
+ showLabel: true,
29
+ onChange: () => {}
30
+ }
31
+ );
32
+
33
+ const container = await fixture(
34
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
35
+ );
36
+ await assertScreenshot(
37
+ 'field-renderer/text-with-label',
38
+ getClip(container as HTMLElement)
39
+ );
40
+ });
41
+
42
+ it('should render evaluated text field with completion', async () => {
43
+ const config: TextFieldConfig = {
44
+ type: 'text',
45
+ label: 'Dynamic URL',
46
+ evaluated: true,
47
+ placeholder: 'Enter URL with expressions'
48
+ };
49
+
50
+ const template = FieldRenderer.renderField(
51
+ 'url',
52
+ config,
53
+ 'https://api.com/@contact.name',
54
+ {
55
+ errors: [],
56
+ showLabel: true,
57
+ onChange: () => {}
58
+ }
59
+ );
60
+
61
+ const container = await fixture(
62
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
63
+ );
64
+ await assertScreenshot(
65
+ 'field-renderer/text-evaluated',
66
+ getClip(container as HTMLElement)
67
+ );
68
+ });
69
+
70
+ it('should render text field without label for ArrayEditor context', async () => {
71
+ const config: TextFieldConfig = {
72
+ type: 'text',
73
+ label: 'Item Name',
74
+ placeholder: 'Enter name'
75
+ };
76
+
77
+ const template = FieldRenderer.renderField(
78
+ 'name',
79
+ config,
80
+ 'Sample Item',
81
+ {
82
+ errors: [],
83
+ showLabel: false,
84
+ flavor: 'small',
85
+ extraClasses: 'form-control',
86
+ onChange: () => {}
87
+ }
88
+ );
89
+
90
+ const container = await fixture(
91
+ html`<div style="width: 300px; padding: 10px;">${template}</div>`
92
+ );
93
+ await assertScreenshot(
94
+ 'field-renderer/text-no-label',
95
+ getClip(container as HTMLElement)
96
+ );
97
+ });
98
+
99
+ it('should render text field with errors', async () => {
100
+ const config: TextFieldConfig = {
101
+ type: 'text',
102
+ label: 'Email Address',
103
+ placeholder: 'Enter email'
104
+ };
105
+
106
+ const template = FieldRenderer.renderField(
107
+ 'email',
108
+ config,
109
+ 'invalid-email',
110
+ {
111
+ errors: ['Invalid email format', 'Email is required'],
112
+ showLabel: true,
113
+ onChange: () => {}
114
+ }
115
+ );
116
+
117
+ const container = await fixture(
118
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
119
+ );
120
+ await assertScreenshot(
121
+ 'field-renderer/text-with-errors',
122
+ getClip(container as HTMLElement)
123
+ );
124
+ });
125
+ });
126
+
127
+ describe('textarea fields', () => {
128
+ it('should render textarea with label', async () => {
129
+ const config: TextareaFieldConfig = {
130
+ type: 'textarea',
131
+ label: 'Description',
132
+ placeholder: 'Enter description',
133
+ rows: 4
134
+ };
135
+
136
+ const template = FieldRenderer.renderField(
137
+ 'description',
138
+ config,
139
+ 'This is a sample description\nwith multiple lines',
140
+ {
141
+ errors: [],
142
+ showLabel: true,
143
+ onChange: () => {}
144
+ }
145
+ );
146
+
147
+ const container = await fixture(
148
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
149
+ );
150
+ await assertScreenshot(
151
+ 'field-renderer/textarea-with-label',
152
+ getClip(container as HTMLElement)
153
+ );
154
+ });
155
+
156
+ it('should render evaluated textarea with completion', async () => {
157
+ const config: TextareaFieldConfig = {
158
+ type: 'textarea',
159
+ label: 'Message Template',
160
+ evaluated: true,
161
+ placeholder: 'Enter message with expressions',
162
+ rows: 3
163
+ };
164
+
165
+ const template = FieldRenderer.renderField(
166
+ 'message',
167
+ config,
168
+ 'Hello @contact.name,\nYour order is ready!',
169
+ {
170
+ errors: [],
171
+ showLabel: true,
172
+ onChange: () => {}
173
+ }
174
+ );
175
+
176
+ const container = await fixture(
177
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
178
+ );
179
+ await assertScreenshot(
180
+ 'field-renderer/textarea-evaluated',
181
+ getClip(container as HTMLElement)
182
+ );
183
+ });
184
+ });
185
+
186
+ describe('select fields', () => {
187
+ it('should render select field with label', async () => {
188
+ const config: SelectFieldConfig = {
189
+ type: 'select',
190
+ label: 'Country',
191
+ options: ['United States', 'Canada', 'United Kingdom', 'Australia'],
192
+ searchable: true
193
+ };
194
+
195
+ const template = FieldRenderer.renderField('country', config, 'Canada', {
196
+ errors: [],
197
+ showLabel: true,
198
+ onChange: () => {}
199
+ });
200
+
201
+ const container = await fixture(
202
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
203
+ );
204
+ await assertScreenshot(
205
+ 'field-renderer/select-with-label',
206
+ getClip(container as HTMLElement)
207
+ );
208
+ });
209
+
210
+ it('should render multi-select field', async () => {
211
+ const config: SelectFieldConfig = {
212
+ type: 'select',
213
+ label: 'Skills',
214
+ options: ['JavaScript', 'Python', 'TypeScript', 'React', 'Node.js'],
215
+ multi: true,
216
+ tags: true
217
+ };
218
+
219
+ const template = FieldRenderer.renderField(
220
+ 'skills',
221
+ config,
222
+ ['JavaScript', 'TypeScript'],
223
+ {
224
+ errors: [],
225
+ showLabel: true,
226
+ onChange: () => {}
227
+ }
228
+ );
229
+
230
+ const container = await fixture(
231
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
232
+ );
233
+ await assertScreenshot(
234
+ 'field-renderer/select-multi',
235
+ getClip(container as HTMLElement)
236
+ );
237
+ });
238
+
239
+ it('should render select field without label for ArrayEditor context', async () => {
240
+ const config: SelectFieldConfig = {
241
+ type: 'select',
242
+ label: 'Status',
243
+ options: ['Active', 'Inactive', 'Pending']
244
+ };
245
+
246
+ const template = FieldRenderer.renderField('status', config, 'Active', {
247
+ errors: [],
248
+ showLabel: false,
249
+ flavor: 'small',
250
+ extraClasses: 'form-control',
251
+ onChange: () => {}
252
+ });
253
+
254
+ const container = await fixture(
255
+ html`<div style="width: 200px; padding: 10px;">${template}</div>`
256
+ );
257
+ await assertScreenshot(
258
+ 'field-renderer/select-no-label',
259
+ getClip(container as HTMLElement)
260
+ );
261
+ });
262
+ });
263
+
264
+ describe('checkbox fields', () => {
265
+ it('should render checkbox with label', async () => {
266
+ const config: CheckboxFieldConfig = {
267
+ type: 'checkbox',
268
+ label: 'Accept Terms and Conditions'
269
+ };
270
+
271
+ const template = FieldRenderer.renderField('accept', config, true, {
272
+ errors: [],
273
+ showLabel: true,
274
+ onChange: () => {}
275
+ });
276
+
277
+ const container = await fixture(
278
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
279
+ );
280
+ await assertScreenshot(
281
+ 'field-renderer/checkbox-checked',
282
+ getClip(container as HTMLElement)
283
+ );
284
+ });
285
+
286
+ it('should render unchecked checkbox', async () => {
287
+ const config: CheckboxFieldConfig = {
288
+ type: 'checkbox',
289
+ label: 'Subscribe to newsletter'
290
+ };
291
+
292
+ const template = FieldRenderer.renderField('subscribe', config, false, {
293
+ errors: [],
294
+ showLabel: true,
295
+ onChange: () => {}
296
+ });
297
+
298
+ const container = await fixture(
299
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
300
+ );
301
+ await assertScreenshot(
302
+ 'field-renderer/checkbox-unchecked',
303
+ getClip(container as HTMLElement)
304
+ );
305
+ });
306
+
307
+ it('should render checkbox with errors', async () => {
308
+ const config: CheckboxFieldConfig = {
309
+ type: 'checkbox',
310
+ label: 'Agree to terms',
311
+ required: true
312
+ };
313
+
314
+ const template = FieldRenderer.renderField('terms', config, false, {
315
+ errors: ['You must accept the terms to continue'],
316
+ showLabel: true,
317
+ onChange: () => {}
318
+ });
319
+
320
+ const container = await fixture(
321
+ html`<div style="width: 400px; padding: 20px;">${template}</div>`
322
+ );
323
+ await assertScreenshot(
324
+ 'field-renderer/checkbox-with-errors',
325
+ getClip(container as HTMLElement)
326
+ );
327
+ });
328
+ });
329
+
330
+ describe('key-value fields', () => {
331
+ it('should render key-value field with label', async () => {
332
+ const config: KeyValueFieldConfig = {
333
+ type: 'key-value',
334
+ label: 'HTTP Headers',
335
+ keyPlaceholder: 'Header name',
336
+ valuePlaceholder: 'Header value'
337
+ };
338
+
339
+ const template = FieldRenderer.renderField(
340
+ 'headers',
341
+ config,
342
+ [
343
+ { key: 'Content-Type', value: 'application/json' },
344
+ { key: 'Authorization', value: 'Bearer token123' }
345
+ ],
346
+ {
347
+ errors: [],
348
+ showLabel: true,
349
+ onChange: () => {}
350
+ }
351
+ );
352
+
353
+ const container = await fixture(
354
+ html`<div style="width: 500px; padding: 20px;">${template}</div>`
355
+ );
356
+ await assertScreenshot(
357
+ 'field-renderer/key-value-with-label',
358
+ getClip(container as HTMLElement)
359
+ );
360
+ });
361
+ });
362
+
363
+ describe('message-editor fields', () => {
364
+ it('should render message-editor with label', async () => {
365
+ const config: MessageEditorFieldConfig = {
366
+ type: 'message-editor',
367
+ label: 'Email Message',
368
+ placeholder: 'Enter your message...',
369
+ minHeight: 120
370
+ };
371
+
372
+ const template = FieldRenderer.renderField(
373
+ 'message',
374
+ config,
375
+ 'Hello! This is a test message.',
376
+ {
377
+ errors: [],
378
+ showLabel: true,
379
+ onChange: () => {},
380
+ additionalData: { attachments: [] }
381
+ }
382
+ );
383
+
384
+ const container = await fixture(
385
+ html`<div style="width: 500px; padding: 20px;">${template}</div>`
386
+ );
387
+ await assertScreenshot(
388
+ 'field-renderer/message-editor-with-label',
389
+ getClip(container as HTMLElement)
390
+ );
391
+ });
392
+ });
393
+
394
+ describe('field consistency', () => {
395
+ it('should render the same field type consistently between contexts', async () => {
396
+ const config: TextFieldConfig = {
397
+ type: 'text',
398
+ label: 'Product Name',
399
+ placeholder: 'Enter product name'
400
+ };
401
+
402
+ // NodeEditor context (with label)
403
+ const nodeEditorTemplate = FieldRenderer.renderField(
404
+ 'product',
405
+ config,
406
+ 'iPhone 15',
407
+ {
408
+ errors: [],
409
+ showLabel: true,
410
+ onChange: () => {}
411
+ }
412
+ );
413
+
414
+ // ArrayEditor context (without label)
415
+ const arrayEditorTemplate = FieldRenderer.renderField(
416
+ 'product',
417
+ config,
418
+ 'iPhone 15',
419
+ {
420
+ errors: [],
421
+ showLabel: false,
422
+ flavor: 'small',
423
+ extraClasses: 'form-control',
424
+ onChange: () => {}
425
+ }
426
+ );
427
+
428
+ const nodeContainer = await fixture(html`<div
429
+ style="width: 400px; padding: 20px; border: 1px solid #ddd; margin: 10px;"
430
+ >
431
+ <h3 style="margin: 0 0 10px 0; font-size: 14px; color: #666;">
432
+ NodeEditor Context
433
+ </h3>
434
+ ${nodeEditorTemplate}
435
+ </div>`);
436
+
437
+ const arrayContainer = await fixture(html`<div
438
+ style="width: 400px; padding: 20px; border: 1px solid #ddd; margin: 10px;"
439
+ >
440
+ <h3 style="margin: 0 0 10px 0; font-size: 14px; color: #666;">
441
+ ArrayEditor Context
442
+ </h3>
443
+ ${arrayEditorTemplate}
444
+ </div>`);
445
+
446
+ const combinedContainer = await fixture(html`<div
447
+ style="display: flex; flex-direction: column; width: 420px;"
448
+ >
449
+ ${nodeContainer} ${arrayContainer}
450
+ </div>`);
451
+
452
+ await assertScreenshot(
453
+ 'field-renderer/context-comparison',
454
+ getClip(combinedContainer as HTMLElement)
455
+ );
456
+ });
457
+ });
458
+
459
+ describe('error handling', () => {
460
+ it('should handle all field types without throwing errors', () => {
461
+ const configs = [
462
+ { type: 'text', label: 'Text' } as TextFieldConfig,
463
+ { type: 'textarea', label: 'Textarea' } as TextareaFieldConfig,
464
+ { type: 'select', label: 'Select', options: [] } as SelectFieldConfig,
465
+ { type: 'checkbox', label: 'Checkbox' },
466
+ { type: 'key-value', label: 'KeyValue' },
467
+ { type: 'array', label: 'Array', itemConfig: {} },
468
+ { type: 'message-editor', label: 'MessageEditor' }
469
+ ];
470
+
471
+ configs.forEach((config, index) => {
472
+ expect(() => {
473
+ FieldRenderer.renderField(`field${index}`, config as any, null, {
474
+ errors: [],
475
+ showLabel: true,
476
+ onChange: () => {}
477
+ });
478
+ }).to.not.throw;
479
+ });
480
+ });
481
+ });
482
+ });
@@ -41,7 +41,7 @@ describe('markdown', () => {
41
41
  };
42
42
 
43
43
  expect(() => new RenderMarkdown(invalidPartInfo as any)).to.throw(
44
- 'renderMarkdown only supports child expressions'
44
+ 'markdown directives only support child expressions'
45
45
  );
46
46
  });
47
47