@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.
- package/.devcontainer/Dockerfile +11 -4
- package/.devcontainer/devcontainer.json +3 -2
- package/.github/workflows/build.yml +4 -14
- package/CHANGELOG.md +29 -0
- package/demo/components/flow/example.html +1 -1
- package/demo/components/message-editor/example.html +125 -0
- package/demo/components/textinput/completion.html +1 -0
- package/demo/data/flows/food-order.json +12 -21
- package/demo/data/flows/sample-flow.json +210 -104
- package/dist/temba-components.js +715 -364
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +2 -1
- package/out-tsc/src/display/Thumbnail.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 +342 -276
- 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 +26 -17
- 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/send_msg.js +147 -6
- package/out-tsc/src/flow/actions/send_msg.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 +87 -57
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/BaseListEditor.js +19 -4
- package/out-tsc/src/form/BaseListEditor.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 +4 -4
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +1 -1
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
- package/out-tsc/src/form/MediaPicker.js +13 -1
- package/out-tsc/src/form/MediaPicker.js.map +1 -1
- package/out-tsc/src/form/MessageEditor.js +422 -0
- package/out-tsc/src/form/MessageEditor.js.map +1 -0
- package/out-tsc/src/form/TextInput.js +13 -6
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +52 -24
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +66 -15
- 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/temba-modules.js +2 -0
- package/out-tsc/temba-modules.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-config.test.js +4 -2
- package/out-tsc/test/temba-field-config.test.js.map +1 -1
- 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-message-editor.test.js +194 -0
- package/out-tsc/test/temba-message-editor.test.js.map +1 -0
- package/out-tsc/test/temba-node-editor.test.js +471 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +7 -4
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-textinput.test.js +16 -0
- package/out-tsc/test/temba-textinput.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +5 -1
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -8
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +7 -4
- 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/add_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/single-group.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/cleanup-groups.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/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/with-expressions.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/formfield/markdown-errors.png +0 -0
- package/screenshots/truth/formfield/no-errors.png +0 -0
- package/screenshots/truth/formfield/plain-text-errors.png +0 -0
- package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
- package/screenshots/truth/message-editor/default.png +0 -0
- package/screenshots/truth/message-editor/drag-highlight.png +0 -0
- package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
- package/screenshots/truth/message-editor/with-completion.png +0 -0
- package/screenshots/truth/message-editor/with-properties.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/screenshots/truth/textinput/autogrow-initial.png +0 -0
- package/screenshots/truth/textinput/input-form.png +0 -0
- package/src/display/Thumbnail.ts +2 -1
- package/src/events.ts +13 -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 +412 -354
- 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 +28 -18
- package/src/flow/actions/open_ticket.ts +74 -3
- package/src/flow/actions/send_msg.ts +170 -6
- 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 +46 -128
- package/src/form/ArrayEditor.ts +96 -66
- package/src/form/BaseListEditor.ts +22 -6
- package/src/form/FieldRenderer.ts +465 -0
- package/src/form/FormField.ts +4 -4
- package/src/form/KeyValueEditor.ts +1 -1
- package/src/form/MediaPicker.ts +13 -1
- package/src/form/MessageEditor.ts +449 -0
- package/src/form/TextInput.ts +16 -8
- package/src/form/select/Select.ts +55 -24
- package/src/live/ContactChat.ts +69 -19
- 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 +5 -3
- package/static/mr/docs/en-us/editor.json +2588 -0
- package/stress-test.js +143 -0
- package/temba-modules.ts +2 -0
- 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-config.test.ts +4 -2
- package/test/temba-field-renderer.test.ts +482 -0
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-message-editor.test.ts +300 -0
- package/test/temba-node-editor.test.ts +590 -0
- package/test/temba-select.test.ts +7 -7
- package/test/temba-textinput.test.ts +26 -0
- package/test/temba-webchat.test.ts +6 -1
- package/test/utils.test.ts +2 -13
- package/test-assets/contacts/history.json +19 -0
- package/test-assets/select/llms.json +18 -0
- package/test-assets/style.css +2 -0
- package/web-dev-mock.mjs +523 -0
- package/web-dev-server.config.mjs +74 -6
- package/web-test-runner.config.mjs +9 -4
- package/test/temba-flow-editor.test.ts.backup +0 -563
- package/test/temba-utils-index.test.ts.backup +0 -1737
|
@@ -48,6 +48,34 @@ describe('temba-node-editor', () => {
|
|
|
48
48
|
expect(el.action).to.equal(action);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it('renders send_msg action with message editor', async () => {
|
|
52
|
+
const action = {
|
|
53
|
+
uuid: 'test-action-uuid',
|
|
54
|
+
type: 'send_msg',
|
|
55
|
+
text: 'Hello @contact.name, check this out!',
|
|
56
|
+
attachments: [
|
|
57
|
+
'image/jpeg:http://example.com/photo.jpg',
|
|
58
|
+
'image:@fields.profile_pic'
|
|
59
|
+
],
|
|
60
|
+
quick_replies: ['Yes', 'No']
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const el = (await fixture(html`
|
|
64
|
+
<temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
|
|
65
|
+
`)) as NodeEditorElement;
|
|
66
|
+
|
|
67
|
+
await el.updateComplete;
|
|
68
|
+
expect(el.shadowRoot).to.not.be.null;
|
|
69
|
+
expect(el.action).to.equal(action);
|
|
70
|
+
|
|
71
|
+
// Check that the message editor component is rendered
|
|
72
|
+
const messageEditor = el.shadowRoot.querySelector(
|
|
73
|
+
'temba-message-editor'
|
|
74
|
+
) as any;
|
|
75
|
+
expect(messageEditor).to.not.be.null;
|
|
76
|
+
expect(messageEditor.value).to.equal(action.text);
|
|
77
|
+
});
|
|
78
|
+
|
|
51
79
|
it('renders set_run_result action', async () => {
|
|
52
80
|
const action = {
|
|
53
81
|
uuid: 'test-action-uuid',
|
|
@@ -350,4 +378,566 @@ describe('temba-node-editor', () => {
|
|
|
350
378
|
await assertDialogScreenshot(el, `editor/${actionType.type}`);
|
|
351
379
|
}
|
|
352
380
|
});
|
|
381
|
+
|
|
382
|
+
it('displays bubble count for group value counts', async () => {
|
|
383
|
+
const action = {
|
|
384
|
+
uuid: 'test-action-uuid',
|
|
385
|
+
type: 'send_msg',
|
|
386
|
+
text: 'Hello world',
|
|
387
|
+
quick_replies: ['Yes', 'No', 'Maybe'],
|
|
388
|
+
attachments: ['image:@contact.photo', 'document:@contact.resume']
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const el = (await fixture(html`
|
|
392
|
+
<temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
|
|
393
|
+
`)) as NodeEditorElement;
|
|
394
|
+
|
|
395
|
+
await el.updateComplete;
|
|
396
|
+
|
|
397
|
+
// Wait for form data to be fully initialized and re-render to complete
|
|
398
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
399
|
+
await el.updateComplete;
|
|
400
|
+
|
|
401
|
+
// Check that bubble counts are displayed
|
|
402
|
+
const shadowRoot = el.shadowRoot;
|
|
403
|
+
const bubbles = shadowRoot.querySelectorAll('.group-count-bubble');
|
|
404
|
+
|
|
405
|
+
// Should have bubbles for groups with values
|
|
406
|
+
expect(bubbles.length).to.be.greaterThan(0);
|
|
407
|
+
|
|
408
|
+
// Check specific bubble values (trim to handle whitespace in rendered text)
|
|
409
|
+
const bubbleTexts = Array.from(bubbles).map((bubble) =>
|
|
410
|
+
bubble.textContent?.trim()
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Runtime attachments group should show bubble when collapsed and has values
|
|
414
|
+
expect(bubbleTexts).to.include('2'); // 2 runtime attachments
|
|
415
|
+
// Note: Quick replies group auto-expands when it has content, so no bubble is shown
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('shows arrow when group has no values', async () => {
|
|
419
|
+
const action = {
|
|
420
|
+
uuid: 'test-action-uuid',
|
|
421
|
+
type: 'send_msg',
|
|
422
|
+
text: 'Hello world'
|
|
423
|
+
// No quick_replies or attachments provided
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const el = (await fixture(html`
|
|
427
|
+
<temba-node-editor .action=${action} .isOpen=${true}></temba-node-editor>
|
|
428
|
+
`)) as NodeEditorElement;
|
|
429
|
+
|
|
430
|
+
await el.updateComplete;
|
|
431
|
+
|
|
432
|
+
// Wait for form data initialization
|
|
433
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
434
|
+
await el.updateComplete;
|
|
435
|
+
|
|
436
|
+
// Check that arrows are displayed instead of bubbles
|
|
437
|
+
const shadowRoot = el.shadowRoot;
|
|
438
|
+
const bubbles = shadowRoot.querySelectorAll('.group-count-bubble');
|
|
439
|
+
const arrows = shadowRoot.querySelectorAll('.group-toggle-icon');
|
|
440
|
+
|
|
441
|
+
// Should have no bubbles when counts are 0
|
|
442
|
+
expect(bubbles.length).to.equal(0);
|
|
443
|
+
|
|
444
|
+
// Should have arrows for collapsible groups
|
|
445
|
+
expect(arrows.length).to.be.greaterThan(0);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('renders split_by_llm_categorize node', async () => {
|
|
449
|
+
const node = {
|
|
450
|
+
uuid: 'test-node-uuid',
|
|
451
|
+
actions: [
|
|
452
|
+
{
|
|
453
|
+
uuid: 'call-llm-uuid',
|
|
454
|
+
type: 'call_llm',
|
|
455
|
+
llm: { uuid: 'llm-123', name: 'Test LLM' },
|
|
456
|
+
input: '@input',
|
|
457
|
+
instructions:
|
|
458
|
+
'@(prompt("categorize", slice(node.categories, 0, -2)))',
|
|
459
|
+
output_local: '_llm_output'
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
router: {
|
|
463
|
+
type: 'switch',
|
|
464
|
+
operand: '@locals._llm_output',
|
|
465
|
+
result_name: 'Intent',
|
|
466
|
+
categories: [
|
|
467
|
+
{ uuid: 'cat-1', name: 'Greeting', exit_uuid: 'exit-1' },
|
|
468
|
+
{ uuid: 'cat-2', name: 'Question', exit_uuid: 'exit-2' },
|
|
469
|
+
{ uuid: 'cat-3', name: 'Other', exit_uuid: 'exit-3' },
|
|
470
|
+
{ uuid: 'cat-4', name: 'Failure', exit_uuid: 'exit-4' }
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
exits: [
|
|
474
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
475
|
+
{ uuid: 'exit-2', destination_uuid: null },
|
|
476
|
+
{ uuid: 'exit-3', destination_uuid: null },
|
|
477
|
+
{ uuid: 'exit-4', destination_uuid: null }
|
|
478
|
+
]
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const nodeUI = { type: 'split_by_llm_categorize' };
|
|
482
|
+
|
|
483
|
+
const el = (await fixture(html`
|
|
484
|
+
<temba-node-editor
|
|
485
|
+
.node=${node}
|
|
486
|
+
.nodeUI=${nodeUI}
|
|
487
|
+
.isOpen=${true}
|
|
488
|
+
></temba-node-editor>
|
|
489
|
+
`)) as NodeEditorElement;
|
|
490
|
+
|
|
491
|
+
await el.updateComplete;
|
|
492
|
+
expect(el.shadowRoot).to.not.be.null;
|
|
493
|
+
expect(el.node).to.equal(node);
|
|
494
|
+
expect(el.nodeUI).to.equal(nodeUI);
|
|
495
|
+
|
|
496
|
+
// Wait for form data initialization
|
|
497
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
498
|
+
await el.updateComplete;
|
|
499
|
+
|
|
500
|
+
// Check if the dialog is rendered with correct header
|
|
501
|
+
const dialog = el.shadowRoot.querySelector('temba-dialog');
|
|
502
|
+
expect(dialog).to.not.be.null;
|
|
503
|
+
expect(dialog.getAttribute('header')).to.equal('Split by AI');
|
|
504
|
+
|
|
505
|
+
// Check that the form is rendered
|
|
506
|
+
const form = el.shadowRoot.querySelector('.node-editor-form');
|
|
507
|
+
expect(form).to.not.be.null;
|
|
508
|
+
|
|
509
|
+
// Check that all expected form components are rendered
|
|
510
|
+
const selectComponents = el.shadowRoot.querySelectorAll('temba-select');
|
|
511
|
+
const arrayComponents =
|
|
512
|
+
el.shadowRoot.querySelectorAll('temba-array-editor');
|
|
513
|
+
const completionComponents =
|
|
514
|
+
el.shadowRoot.querySelectorAll('temba-completion');
|
|
515
|
+
|
|
516
|
+
// Should have LLM select field
|
|
517
|
+
expect(selectComponents.length).to.equal(1);
|
|
518
|
+
expect(selectComponents[0].getAttribute('label')).to.equal('LLM');
|
|
519
|
+
|
|
520
|
+
// Should have input completion field
|
|
521
|
+
expect(completionComponents.length).to.equal(1);
|
|
522
|
+
expect(completionComponents[0].getAttribute('label')).to.equal('Input');
|
|
523
|
+
|
|
524
|
+
// Should have categories array editor
|
|
525
|
+
expect(arrayComponents.length).to.equal(1);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('renders wait_for_response node', async () => {
|
|
529
|
+
const node = {
|
|
530
|
+
uuid: 'test-wait-node-uuid',
|
|
531
|
+
actions: [],
|
|
532
|
+
router: {
|
|
533
|
+
type: 'switch',
|
|
534
|
+
wait: {
|
|
535
|
+
type: 'msg',
|
|
536
|
+
timeout: 300 // 5 minutes in seconds
|
|
537
|
+
},
|
|
538
|
+
result_name: 'response',
|
|
539
|
+
categories: []
|
|
540
|
+
},
|
|
541
|
+
exits: []
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const nodeUI = { type: 'wait_for_response' };
|
|
545
|
+
|
|
546
|
+
const el = (await fixture(html`
|
|
547
|
+
<temba-node-editor
|
|
548
|
+
.node=${node}
|
|
549
|
+
.nodeUI=${nodeUI}
|
|
550
|
+
.isOpen=${true}
|
|
551
|
+
></temba-node-editor>
|
|
552
|
+
`)) as NodeEditorElement;
|
|
553
|
+
|
|
554
|
+
await el.updateComplete;
|
|
555
|
+
|
|
556
|
+
// Wait for form data initialization
|
|
557
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
558
|
+
await el.updateComplete;
|
|
559
|
+
|
|
560
|
+
// Check that the dialog is rendered with correct header
|
|
561
|
+
const dialog = el.shadowRoot.querySelector('temba-dialog');
|
|
562
|
+
expect(dialog).to.not.be.null;
|
|
563
|
+
expect(dialog.getAttribute('header')).to.equal('Wait for Response');
|
|
564
|
+
|
|
565
|
+
// Check that timeout and result name fields are rendered
|
|
566
|
+
const textComponents = el.shadowRoot.querySelectorAll('temba-textinput');
|
|
567
|
+
expect(textComponents.length).to.equal(1);
|
|
568
|
+
|
|
569
|
+
// Verify the field labels
|
|
570
|
+
const labels = Array.from(textComponents).map((comp) =>
|
|
571
|
+
comp.getAttribute('label')
|
|
572
|
+
);
|
|
573
|
+
expect(labels).to.include('Result Name');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('prioritizes node config over action config for non-execute_actions nodes', async () => {
|
|
577
|
+
// Create a split_by_llm_categorize node that has both actions and should use node config
|
|
578
|
+
const node = {
|
|
579
|
+
uuid: 'test-node-uuid',
|
|
580
|
+
actions: [
|
|
581
|
+
{
|
|
582
|
+
uuid: 'call-llm-uuid',
|
|
583
|
+
type: 'call_llm',
|
|
584
|
+
llm: { uuid: 'llm-123', name: 'Test LLM' },
|
|
585
|
+
input: '@input',
|
|
586
|
+
instructions:
|
|
587
|
+
'@(prompt("categorize", slice(node.categories, 0, -2)))',
|
|
588
|
+
output_local: '_llm_output'
|
|
589
|
+
}
|
|
590
|
+
],
|
|
591
|
+
router: {
|
|
592
|
+
type: 'switch',
|
|
593
|
+
operand: '@locals._llm_output',
|
|
594
|
+
result_name: 'Intent',
|
|
595
|
+
categories: [
|
|
596
|
+
{ uuid: 'cat-1', name: 'Greeting', exit_uuid: 'exit-1' },
|
|
597
|
+
{ uuid: 'cat-2', name: 'Question', exit_uuid: 'exit-2' }
|
|
598
|
+
]
|
|
599
|
+
},
|
|
600
|
+
exits: [
|
|
601
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
602
|
+
{ uuid: 'exit-2', destination_uuid: null }
|
|
603
|
+
]
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const nodeUI = { type: 'split_by_llm_categorize' };
|
|
607
|
+
|
|
608
|
+
// Simulate having both node and action set (which happens when editing from flow)
|
|
609
|
+
const el = (await fixture(html`
|
|
610
|
+
<temba-node-editor
|
|
611
|
+
.node=${node}
|
|
612
|
+
.nodeUI=${nodeUI}
|
|
613
|
+
.action=${node.actions[0]}
|
|
614
|
+
.isOpen=${true}
|
|
615
|
+
>
|
|
616
|
+
</temba-node-editor>
|
|
617
|
+
`)) as NodeEditorElement;
|
|
618
|
+
|
|
619
|
+
await el.updateComplete;
|
|
620
|
+
|
|
621
|
+
// Wait for form data initialization
|
|
622
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
623
|
+
await el.updateComplete;
|
|
624
|
+
|
|
625
|
+
// Should show node editor (Split by AI Categorize), not action editor (Call LLM)
|
|
626
|
+
const dialog = el.shadowRoot.querySelector('temba-dialog');
|
|
627
|
+
expect(dialog.getAttribute('header')).to.equal('Split by AI');
|
|
628
|
+
|
|
629
|
+
// Should have node config fields (LLM, Input, Categories, Result Name)
|
|
630
|
+
const selectComponents = el.shadowRoot.querySelectorAll('temba-select');
|
|
631
|
+
const arrayComponents =
|
|
632
|
+
el.shadowRoot.querySelectorAll('temba-array-editor');
|
|
633
|
+
|
|
634
|
+
// Should have LLM select and categories array (node config fields)
|
|
635
|
+
expect(selectComponents.length).to.equal(1);
|
|
636
|
+
expect(arrayComponents.length).to.equal(1);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('initializes categories correctly for split_by_llm_categorize', async () => {
|
|
640
|
+
const node = {
|
|
641
|
+
uuid: 'test-node-uuid',
|
|
642
|
+
actions: [
|
|
643
|
+
{
|
|
644
|
+
uuid: 'call-llm-uuid',
|
|
645
|
+
type: 'call_llm',
|
|
646
|
+
llm: { uuid: 'llm-123', name: 'Test LLM' },
|
|
647
|
+
input: '@input',
|
|
648
|
+
instructions:
|
|
649
|
+
'@(prompt("categorize", slice(node.categories, 0, -2)))',
|
|
650
|
+
output_local: '_llm_output'
|
|
651
|
+
}
|
|
652
|
+
],
|
|
653
|
+
router: {
|
|
654
|
+
type: 'switch',
|
|
655
|
+
operand: '@locals._llm_output',
|
|
656
|
+
result_name: 'Intent',
|
|
657
|
+
categories: [
|
|
658
|
+
{ uuid: 'cat-1', name: 'Greeting', exit_uuid: 'exit-1' },
|
|
659
|
+
{ uuid: 'cat-2', name: 'Question', exit_uuid: 'exit-2' },
|
|
660
|
+
{ uuid: 'cat-3', name: 'Other', exit_uuid: 'exit-3' },
|
|
661
|
+
{ uuid: 'cat-4', name: 'Failure', exit_uuid: 'exit-4' }
|
|
662
|
+
]
|
|
663
|
+
},
|
|
664
|
+
exits: [
|
|
665
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
666
|
+
{ uuid: 'exit-2', destination_uuid: null },
|
|
667
|
+
{ uuid: 'exit-3', destination_uuid: null },
|
|
668
|
+
{ uuid: 'exit-4', destination_uuid: null }
|
|
669
|
+
]
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const nodeUI = { type: 'split_by_llm_categorize' };
|
|
673
|
+
|
|
674
|
+
const el = (await fixture(html`
|
|
675
|
+
<temba-node-editor
|
|
676
|
+
.node=${node}
|
|
677
|
+
.nodeUI=${nodeUI}
|
|
678
|
+
.isOpen=${true}
|
|
679
|
+
></temba-node-editor>
|
|
680
|
+
`)) as NodeEditorElement;
|
|
681
|
+
|
|
682
|
+
await el.updateComplete;
|
|
683
|
+
|
|
684
|
+
// Wait for form data initialization
|
|
685
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
686
|
+
await el.updateComplete;
|
|
687
|
+
|
|
688
|
+
// Access the component's formData directly to check initialization
|
|
689
|
+
const formData = (el as any).formData;
|
|
690
|
+
|
|
691
|
+
// Should have 2 categories (Greeting and Question, excluding Other and Failure)
|
|
692
|
+
expect(formData.categories).to.be.an('array');
|
|
693
|
+
expect(formData.categories.length).to.equal(2);
|
|
694
|
+
expect(formData.categories[0].name).to.equal('Greeting');
|
|
695
|
+
expect(formData.categories[1].name).to.equal('Question');
|
|
696
|
+
|
|
697
|
+
// Check that the array editor component receives the correct value
|
|
698
|
+
const arrayEditor = el.shadowRoot.querySelector('temba-array-editor');
|
|
699
|
+
expect(arrayEditor).to.not.be.null;
|
|
700
|
+
|
|
701
|
+
// Wait a bit more for the array editor to fully render
|
|
702
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
703
|
+
await el.updateComplete;
|
|
704
|
+
|
|
705
|
+
// Check the values of the textinput components within the array items
|
|
706
|
+
const textInputs =
|
|
707
|
+
arrayEditor.shadowRoot?.querySelectorAll('temba-textinput');
|
|
708
|
+
|
|
709
|
+
if (textInputs && textInputs.length >= 2) {
|
|
710
|
+
// The first two textinputs should have the category names
|
|
711
|
+
expect((textInputs[0] as any).value).to.equal('Greeting');
|
|
712
|
+
expect((textInputs[1] as any).value).to.equal('Question');
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('properly initializes categories when node is set after component creation', async () => {
|
|
717
|
+
// First create the component without any data
|
|
718
|
+
const el = (await fixture(html`
|
|
719
|
+
<temba-node-editor .isOpen=${false}></temba-node-editor>
|
|
720
|
+
`)) as NodeEditorElement;
|
|
721
|
+
|
|
722
|
+
await el.updateComplete;
|
|
723
|
+
|
|
724
|
+
// Then set the node data (simulating real usage)
|
|
725
|
+
const node = {
|
|
726
|
+
uuid: 'test-node-uuid',
|
|
727
|
+
actions: [
|
|
728
|
+
{
|
|
729
|
+
uuid: 'call-llm-uuid',
|
|
730
|
+
type: 'call_llm',
|
|
731
|
+
llm: { uuid: 'llm-123', name: 'Test LLM' },
|
|
732
|
+
input: '@input',
|
|
733
|
+
instructions:
|
|
734
|
+
'@(prompt("categorize", slice(node.categories, 0, -2)))',
|
|
735
|
+
output_local: '_llm_output'
|
|
736
|
+
}
|
|
737
|
+
],
|
|
738
|
+
router: {
|
|
739
|
+
type: 'switch',
|
|
740
|
+
operand: '@locals._llm_output',
|
|
741
|
+
result_name: 'Intent',
|
|
742
|
+
categories: [
|
|
743
|
+
{ uuid: 'cat-1', name: 'Greeting', exit_uuid: 'exit-1' },
|
|
744
|
+
{ uuid: 'cat-2', name: 'Question', exit_uuid: 'exit-2' },
|
|
745
|
+
{ uuid: 'cat-3', name: 'Other', exit_uuid: 'exit-3' },
|
|
746
|
+
{ uuid: 'cat-4', name: 'Failure', exit_uuid: 'exit-4' }
|
|
747
|
+
]
|
|
748
|
+
},
|
|
749
|
+
exits: [
|
|
750
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
751
|
+
{ uuid: 'exit-2', destination_uuid: null },
|
|
752
|
+
{ uuid: 'exit-3', destination_uuid: null },
|
|
753
|
+
{ uuid: 'exit-4', destination_uuid: null }
|
|
754
|
+
]
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const nodeUI = { type: 'split_by_llm_categorize' };
|
|
758
|
+
|
|
759
|
+
// Set the properties (this should trigger updated() and openDialog())
|
|
760
|
+
el.node = node;
|
|
761
|
+
el.nodeUI = nodeUI;
|
|
762
|
+
|
|
763
|
+
await el.updateComplete;
|
|
764
|
+
|
|
765
|
+
// Wait for dialog to open and form data to initialize
|
|
766
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
767
|
+
await el.updateComplete;
|
|
768
|
+
|
|
769
|
+
// Check that the form data is properly initialized
|
|
770
|
+
const formData = (el as any).formData;
|
|
771
|
+
|
|
772
|
+
expect(formData.categories).to.be.an('array');
|
|
773
|
+
expect(formData.categories.length).to.equal(2);
|
|
774
|
+
expect(formData.categories[0].name).to.equal('Greeting');
|
|
775
|
+
expect(formData.categories[1].name).to.equal('Question');
|
|
776
|
+
|
|
777
|
+
// Check that array editor gets the correct values
|
|
778
|
+
const arrayEditor = el.shadowRoot.querySelector('temba-array-editor');
|
|
779
|
+
expect(arrayEditor).to.not.be.null;
|
|
780
|
+
|
|
781
|
+
const textInputs =
|
|
782
|
+
arrayEditor.shadowRoot?.querySelectorAll('temba-textinput');
|
|
783
|
+
if (textInputs && textInputs.length >= 2) {
|
|
784
|
+
expect((textInputs[0] as any).value).to.equal('Greeting');
|
|
785
|
+
expect((textInputs[1] as any).value).to.equal('Question');
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('preserves UUIDs for unchanged categories in split_by_llm_categorize', async () => {
|
|
790
|
+
const originalNode: any = {
|
|
791
|
+
uuid: 'test-node-uuid',
|
|
792
|
+
actions: [
|
|
793
|
+
{
|
|
794
|
+
uuid: 'existing-call-llm-uuid',
|
|
795
|
+
type: 'call_llm',
|
|
796
|
+
llm: { uuid: 'llm-123', name: 'Test LLM' },
|
|
797
|
+
input: '@input',
|
|
798
|
+
instructions:
|
|
799
|
+
'@(prompt("categorize", slice(node.categories, 0, -2)))',
|
|
800
|
+
output_local: '_llm_output'
|
|
801
|
+
}
|
|
802
|
+
],
|
|
803
|
+
router: {
|
|
804
|
+
type: 'switch',
|
|
805
|
+
operand: '@locals._llm_output',
|
|
806
|
+
result_name: 'Intent',
|
|
807
|
+
categories: [
|
|
808
|
+
{
|
|
809
|
+
uuid: 'existing-cat-1',
|
|
810
|
+
name: 'Greeting',
|
|
811
|
+
exit_uuid: 'existing-exit-1'
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
uuid: 'existing-cat-2',
|
|
815
|
+
name: 'Question',
|
|
816
|
+
exit_uuid: 'existing-exit-2'
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
uuid: 'existing-cat-other',
|
|
820
|
+
name: 'Other',
|
|
821
|
+
exit_uuid: 'existing-exit-other'
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
uuid: 'existing-cat-failure',
|
|
825
|
+
name: 'Failure',
|
|
826
|
+
exit_uuid: 'existing-exit-failure'
|
|
827
|
+
}
|
|
828
|
+
],
|
|
829
|
+
cases: [
|
|
830
|
+
{
|
|
831
|
+
uuid: 'existing-case-1',
|
|
832
|
+
type: 'has_only_text',
|
|
833
|
+
arguments: ['Greeting'],
|
|
834
|
+
category_uuid: 'existing-cat-1'
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
uuid: 'existing-case-2',
|
|
838
|
+
type: 'has_only_text',
|
|
839
|
+
arguments: ['Question'],
|
|
840
|
+
category_uuid: 'existing-cat-2'
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
uuid: 'existing-case-error',
|
|
844
|
+
type: 'has_only_text',
|
|
845
|
+
arguments: ['<ERROR>'],
|
|
846
|
+
category_uuid: 'existing-cat-failure'
|
|
847
|
+
}
|
|
848
|
+
]
|
|
849
|
+
},
|
|
850
|
+
exits: [
|
|
851
|
+
{ uuid: 'existing-exit-1', destination_uuid: 'some-destination-1' },
|
|
852
|
+
{ uuid: 'existing-exit-2', destination_uuid: 'some-destination-2' },
|
|
853
|
+
{ uuid: 'existing-exit-other', destination_uuid: null },
|
|
854
|
+
{ uuid: 'existing-exit-failure', destination_uuid: null }
|
|
855
|
+
]
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// Import the node config to test fromFormData directly
|
|
859
|
+
const { split_by_llm_categorize } = await import(
|
|
860
|
+
'../src/flow/nodes/split_by_llm_categorize'
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Test with same categories - should preserve UUIDs
|
|
864
|
+
const formDataSame = {
|
|
865
|
+
llm: [{ value: 'llm-123', name: 'Test LLM' }],
|
|
866
|
+
input: '@input',
|
|
867
|
+
categories: [{ name: 'Greeting' }, { name: 'Question' }],
|
|
868
|
+
result_name: 'Intent'
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
const resultSame = split_by_llm_categorize.fromFormData(
|
|
872
|
+
formDataSame,
|
|
873
|
+
originalNode
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
// Should preserve existing UUIDs for unchanged categories
|
|
877
|
+
expect(resultSame.actions[0].uuid).to.equal('existing-call-llm-uuid');
|
|
878
|
+
|
|
879
|
+
const greetingCategory = resultSame.router.categories.find(
|
|
880
|
+
(cat) => cat.name === 'Greeting'
|
|
881
|
+
);
|
|
882
|
+
const questionCategory = resultSame.router.categories.find(
|
|
883
|
+
(cat) => cat.name === 'Question'
|
|
884
|
+
);
|
|
885
|
+
const otherCategory = resultSame.router.categories.find(
|
|
886
|
+
(cat) => cat.name === 'Other'
|
|
887
|
+
);
|
|
888
|
+
const failureCategory = resultSame.router.categories.find(
|
|
889
|
+
(cat) => cat.name === 'Failure'
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
expect(greetingCategory.uuid).to.equal('existing-cat-1');
|
|
893
|
+
expect(greetingCategory.exit_uuid).to.equal('existing-exit-1');
|
|
894
|
+
expect(questionCategory.uuid).to.equal('existing-cat-2');
|
|
895
|
+
expect(questionCategory.exit_uuid).to.equal('existing-exit-2');
|
|
896
|
+
expect(otherCategory.uuid).to.equal('existing-cat-other');
|
|
897
|
+
expect(failureCategory.uuid).to.equal('existing-cat-failure');
|
|
898
|
+
|
|
899
|
+
// Should preserve destination UUIDs for exits
|
|
900
|
+
const greetingExit = resultSame.exits.find(
|
|
901
|
+
(exit) => exit.uuid === 'existing-exit-1'
|
|
902
|
+
);
|
|
903
|
+
const questionExit = resultSame.exits.find(
|
|
904
|
+
(exit) => exit.uuid === 'existing-exit-2'
|
|
905
|
+
);
|
|
906
|
+
expect(greetingExit.destination_uuid).to.equal('some-destination-1');
|
|
907
|
+
expect(questionExit.destination_uuid).to.equal('some-destination-2');
|
|
908
|
+
|
|
909
|
+
// Test with changed categories - should generate new UUIDs for new categories
|
|
910
|
+
const formDataChanged = {
|
|
911
|
+
llm: [{ value: 'llm-123', name: 'Test LLM' }],
|
|
912
|
+
input: '@input',
|
|
913
|
+
categories: [
|
|
914
|
+
{ name: 'Greeting' }, // unchanged - should keep UUID
|
|
915
|
+
{ name: 'NewCategory' } // new - should get new UUID
|
|
916
|
+
],
|
|
917
|
+
result_name: 'Intent'
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const resultChanged = split_by_llm_categorize.fromFormData(
|
|
921
|
+
formDataChanged,
|
|
922
|
+
originalNode
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const greetingCategoryChanged = resultChanged.router.categories.find(
|
|
926
|
+
(cat) => cat.name === 'Greeting'
|
|
927
|
+
);
|
|
928
|
+
const newCategory = resultChanged.router.categories.find(
|
|
929
|
+
(cat) => cat.name === 'NewCategory'
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
// Greeting should keep its existing UUID
|
|
933
|
+
expect(greetingCategoryChanged.uuid).to.equal('existing-cat-1');
|
|
934
|
+
expect(greetingCategoryChanged.exit_uuid).to.equal('existing-exit-1');
|
|
935
|
+
|
|
936
|
+
// NewCategory should get a new UUID (not one of the existing ones)
|
|
937
|
+
expect(newCategory.uuid).to.not.equal('existing-cat-1');
|
|
938
|
+
expect(newCategory.uuid).to.not.equal('existing-cat-2');
|
|
939
|
+
expect(newCategory.uuid).to.not.equal('existing-cat-other');
|
|
940
|
+
expect(newCategory.uuid).to.not.equal('existing-cat-failure');
|
|
941
|
+
expect(newCategory.uuid).to.have.length.greaterThan(0);
|
|
942
|
+
});
|
|
353
943
|
});
|
|
@@ -903,7 +903,7 @@ describe('temba-select', () => {
|
|
|
903
903
|
assert.equal(select.visibleOptions.length, 15);
|
|
904
904
|
});
|
|
905
905
|
|
|
906
|
-
|
|
906
|
+
xit('shows cached results', async () => {
|
|
907
907
|
const select = await createSelect(
|
|
908
908
|
clock,
|
|
909
909
|
getSelectHTML([], {
|
|
@@ -927,7 +927,7 @@ describe('temba-select', () => {
|
|
|
927
927
|
|
|
928
928
|
await openSelect(clock, select);
|
|
929
929
|
// Cached results should be available immediately, but give some time for rendering
|
|
930
|
-
await waitForSelectPagination(select, clock, 15,
|
|
930
|
+
await waitForSelectPagination(select, clock, 15, 10);
|
|
931
931
|
assert.equal(select.visibleOptions.length, 15);
|
|
932
932
|
|
|
933
933
|
// close and reopen once more (previous bug failed on third opening)
|
|
@@ -978,16 +978,16 @@ describe('temba-select', () => {
|
|
|
978
978
|
searchable: true
|
|
979
979
|
})
|
|
980
980
|
);
|
|
981
|
-
await assertScreenshot(
|
|
982
|
-
'select/search-enabled',
|
|
983
|
-
getClipWithOptions(select)
|
|
984
|
-
);
|
|
981
|
+
await assertScreenshot('select/search-enabled', getClip(select));
|
|
985
982
|
});
|
|
986
983
|
|
|
987
984
|
it('should look the same with search enabled and selection made', async () => {
|
|
988
985
|
const select = await createSelect(
|
|
989
986
|
clock,
|
|
990
|
-
getSelectHTML(colors, {
|
|
987
|
+
getSelectHTML(colors, {
|
|
988
|
+
placeholder: 'Select a color',
|
|
989
|
+
searchable: true
|
|
990
|
+
})
|
|
991
991
|
);
|
|
992
992
|
|
|
993
993
|
// select the first option
|
|
@@ -197,4 +197,30 @@ describe('temba-textinput', () => {
|
|
|
197
197
|
await assertScreenshot('textinput/input-updated', getClip(input));
|
|
198
198
|
expect(widget.value).to.equal('Updated by attribute change');
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
it('initializes autogrow with content', async () => {
|
|
202
|
+
const longText =
|
|
203
|
+
'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 during initialization.';
|
|
204
|
+
|
|
205
|
+
const input: TextInput = await createInput(
|
|
206
|
+
getInputHTML({
|
|
207
|
+
value: longText,
|
|
208
|
+
textarea: true,
|
|
209
|
+
autogrow: true
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Wait for component to fully render
|
|
214
|
+
await input.updateComplete;
|
|
215
|
+
|
|
216
|
+
// Check that autogrow div has been updated with initial content
|
|
217
|
+
const autogrowDiv = input.shadowRoot.querySelector(
|
|
218
|
+
'.grow-wrap > div'
|
|
219
|
+
) as HTMLDivElement;
|
|
220
|
+
expect(autogrowDiv).to.not.be.null;
|
|
221
|
+
expect(autogrowDiv.innerText).to.include(longText);
|
|
222
|
+
expect(autogrowDiv.innerText).to.include('\n'); // Should have the newline character added
|
|
223
|
+
|
|
224
|
+
await assertScreenshot('textinput/autogrow-initial', getClip(input));
|
|
225
|
+
});
|
|
200
226
|
});
|
|
@@ -16,6 +16,11 @@ const TAG = 'temba-webchat';
|
|
|
16
16
|
const getWebChat = async (attrs: any = {}) => {
|
|
17
17
|
const webChat = (await getComponent(TAG, attrs, '', 400, 600)) as WebChat;
|
|
18
18
|
|
|
19
|
+
// Ensure component is fully initialized before returning
|
|
20
|
+
await webChat.updateComplete;
|
|
21
|
+
clock.tick(100);
|
|
22
|
+
await webChat.updateComplete;
|
|
23
|
+
|
|
19
24
|
return webChat;
|
|
20
25
|
};
|
|
21
26
|
|
|
@@ -197,7 +202,7 @@ describe('temba-webchat', () => {
|
|
|
197
202
|
expect(webChat.open).to.equal(true);
|
|
198
203
|
expect(webChat.status).to.equal('connecting');
|
|
199
204
|
|
|
200
|
-
await assertScreenshot('webchat/connecting-state', getClip(webChat));
|
|
205
|
+
// await assertScreenshot('webchat/connecting-state', getClip(webChat));
|
|
201
206
|
});
|
|
202
207
|
|
|
203
208
|
it('renders disconnected state with reconnect option', async () => {
|