@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.
- package/CHANGELOG.md +27 -3
- package/demo/data/flows/sample-flow.json +186 -96
- package/dist/temba-components.js +414 -351
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/excellent/helpers.js +2 -2
- package/out-tsc/src/excellent/helpers.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +25 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +11 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +133 -290
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/call_llm.js +56 -3
- package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
- package/out-tsc/src/flow/actions/open_ticket.js +65 -3
- package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +75 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/config.js +4 -0
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +0 -65
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +18 -61
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +305 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -0
- package/out-tsc/src/form/FormField.js +3 -3
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/TextInput.js +1 -1
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +48 -20
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +39 -13
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/markdown.js +13 -11
- package/out-tsc/src/markdown.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +2 -0
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +148 -0
- package/out-tsc/test/NodeHelper.js.map +1 -0
- package/out-tsc/test/actions/call_llm.test.js +103 -0
- package/out-tsc/test/actions/call_llm.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +150 -0
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
- package/out-tsc/test/temba-add-input-labels.test.js +70 -0
- package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
- package/out-tsc/test/temba-field-renderer.test.js +296 -0
- package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
- package/out-tsc/test/temba-markdown.test.js +1 -1
- package/out-tsc/test/temba-markdown.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +400 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +6 -3
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +1 -1
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/send_msg.png +0 -0
- package/screenshots/truth/editor/set_contact_language.png +0 -0
- package/screenshots/truth/editor/set_contact_name.png +0 -0
- package/screenshots/truth/editor/set_run_result.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/context-comparison.png +0 -0
- package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
- package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
- package/screenshots/truth/field-renderer/select-multi.png +0 -0
- package/screenshots/truth/field-renderer/select-no-label.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/text-no-label.png +0 -0
- package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/text-with-label.png +0 -0
- package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/src/events.ts +8 -1
- package/src/excellent/helpers.ts +2 -2
- package/src/flow/CanvasNode.ts +22 -1
- package/src/flow/Editor.ts +12 -1
- package/src/flow/NodeEditor.ts +186 -374
- package/src/flow/actions/add_input_labels.ts +45 -0
- package/src/flow/actions/call_llm.ts +57 -3
- package/src/flow/actions/call_webhook.ts +1 -1
- package/src/flow/actions/open_ticket.ts +74 -3
- package/src/flow/actions/set_run_result.ts +83 -0
- package/src/flow/config.ts +4 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
- package/src/flow/nodes/split_by_ticket.ts +19 -0
- package/src/flow/nodes/wait_for_response.ts +28 -1
- package/src/flow/types.ts +26 -127
- package/src/form/ArrayEditor.ts +34 -82
- package/src/form/FieldRenderer.ts +465 -0
- package/src/form/FormField.ts +3 -3
- package/src/form/TextInput.ts +1 -1
- package/src/form/select/Select.ts +51 -20
- package/src/live/ContactChat.ts +39 -15
- package/src/markdown.ts +19 -11
- package/src/store/flow-definition.d.ts +5 -2
- package/static/api/labels.json +31 -0
- package/static/api/topics.json +24 -9
- package/static/api/users.json +35 -16
- package/static/css/temba-components.css +3 -3
- package/stress-test.js +18 -13
- package/test/ActionHelper.ts +2 -0
- package/test/NodeHelper.ts +184 -0
- package/test/actions/call_llm.test.ts +137 -0
- package/test/nodes/README.md +78 -0
- package/test/nodes/split_by_llm_categorize.test.ts +698 -0
- package/test/nodes/split_by_random.test.ts +177 -0
- package/test/nodes/wait_for_digits.test.ts +176 -0
- package/test/nodes/wait_for_response.test.ts +206 -0
- package/test/temba-add-input-labels.test.ts +87 -0
- package/test/temba-field-renderer.test.ts +482 -0
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-node-editor.test.ts +496 -0
- package/test/temba-select.test.ts +6 -6
- package/test/temba-webchat.test.ts +1 -1
- package/test-assets/select/llms.json +18 -0
- package/web-dev-mock.mjs +96 -6
- package/web-dev-server.config.mjs +29 -7
- package/test/temba-flow-editor.test.ts.backup +0 -563
- 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
|
+
});
|