@nyaruka/temba-components 0.131.1 → 0.131.2
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/.github/workflows/publish.yml +4 -1
- package/CHANGELOG.md +61 -1
- package/demo/data/flows/food-order.json +2 -2
- package/demo/data/flows/sample-flow.json +74 -125
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1155 -618
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +4 -1
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +200 -0
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
- package/out-tsc/src/flow/CanvasNode.js +327 -19
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +562 -66
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +240 -93
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
- package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +2 -2
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +2 -2
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -2
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +4 -5
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +9 -19
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +3 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +180 -6
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -15
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/currencies.js +45 -0
- package/out-tsc/src/flow/currencies.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared.js +17 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
- package/out-tsc/src/flow/types.js +71 -12
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +101 -14
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +2 -4
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +3 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +98 -33
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +15 -18
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/store/AppState.js +53 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils.js +254 -13
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +3 -3
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +6 -3
- package/out-tsc/test/NodeHelper.js.map +1 -1
- package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
- package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +148 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
- package/out-tsc/test/actions/send_email.test.js +17 -23
- package/out-tsc/test/actions/send_email.test.js.map +1 -1
- package/out-tsc/test/actions/send_msg.test.js +33 -15
- package/out-tsc/test/actions/send_msg.test.js.map +1 -1
- package/out-tsc/test/actions/start_session.test.js +116 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
- package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +3 -3
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
- package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
- package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
- package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
- package/out-tsc/test/temba-canvas-menu.test.js +122 -0
- package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +7 -8
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +3 -1
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +115 -0
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +2 -1
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-sortable-list.test.js +51 -0
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
- package/out-tsc/test/temba-utils-index.test.js +1 -27
- package/out-tsc/test/temba-utils-index.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +2 -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/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/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.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/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/with-attachments.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_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/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/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/canvas-menu/open.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.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/src/Icons.ts +4 -1
- package/src/events.ts +2 -6
- package/src/flow/CanvasMenu.ts +217 -0
- package/src/flow/CanvasNode.ts +408 -10
- package/src/flow/Editor.ts +683 -44
- package/src/flow/NodeEditor.ts +304 -125
- package/src/flow/NodeTypeSelector.ts +592 -0
- package/src/flow/actions/add_contact_groups.ts +4 -4
- package/src/flow/actions/add_contact_urn.ts +76 -4
- package/src/flow/actions/add_input_labels.ts +4 -4
- package/src/flow/actions/play_audio.ts +2 -2
- package/src/flow/actions/remove_contact_groups.ts +14 -6
- package/src/flow/actions/request_optin.ts +2 -2
- package/src/flow/actions/say_msg.ts +2 -2
- package/src/flow/actions/send_broadcast.ts +85 -23
- package/src/flow/actions/send_email.ts +10 -6
- package/src/flow/actions/send_msg.ts +22 -32
- package/src/flow/actions/set_contact_channel.ts +5 -11
- package/src/flow/actions/set_contact_field.ts +20 -25
- package/src/flow/actions/set_contact_language.ts +9 -4
- package/src/flow/actions/set_contact_name.ts +3 -15
- package/src/flow/actions/set_contact_status.ts +3 -3
- package/src/flow/actions/set_run_result.ts +4 -4
- package/src/flow/actions/start_session.ts +208 -6
- package/src/flow/config.ts +13 -15
- package/src/flow/currencies.ts +51 -0
- package/src/flow/nodes/shared-rules.ts +301 -0
- package/src/flow/nodes/shared.ts +18 -0
- package/src/flow/nodes/split_by_airtime.ts +238 -5
- package/src/flow/nodes/split_by_contact_field.ts +185 -3
- package/src/flow/nodes/split_by_expression.ts +94 -2
- package/src/flow/nodes/split_by_groups.ts +15 -10
- package/src/flow/nodes/split_by_intent.ts +7 -0
- package/src/flow/nodes/split_by_llm.ts +4 -3
- package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
- package/src/flow/nodes/split_by_random.ts +5 -5
- package/src/flow/nodes/split_by_resthook.ts +130 -0
- package/src/flow/nodes/split_by_run_result.ts +249 -3
- package/src/flow/nodes/split_by_scheme.ts +192 -2
- package/src/flow/nodes/split_by_subflow.ts +6 -4
- package/src/flow/nodes/split_by_ticket.ts +4 -3
- package/src/flow/nodes/split_by_webhook.ts +6 -5
- package/src/flow/nodes/wait_for_audio.ts +2 -2
- package/src/flow/nodes/wait_for_digits.ts +2 -2
- package/src/flow/nodes/wait_for_image.ts +2 -2
- package/src/flow/nodes/wait_for_location.ts +2 -2
- package/src/flow/nodes/wait_for_menu.ts +2 -2
- package/src/flow/nodes/wait_for_response.ts +48 -679
- package/src/flow/nodes/wait_for_video.ts +2 -2
- package/src/flow/types.ts +109 -23
- package/src/flow/utils.ts +108 -14
- package/src/form/FieldRenderer.ts +2 -4
- package/src/interfaces.ts +3 -0
- package/src/list/SortableList.ts +109 -34
- package/src/live/ContactChat.ts +15 -18
- package/src/store/AppState.ts +69 -0
- package/src/store/flow-definition.d.ts +2 -5
- package/src/utils.ts +332 -12
- package/static/api/channels.json +46 -0
- package/static/api/resthooks.json +31 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/lightning-02.svg +1 -0
- package/static/svg/work/used/lightning-02.svg +3 -0
- package/temba-modules.ts +4 -0
- package/test/ActionHelper.ts +3 -3
- package/test/NodeHelper.ts +6 -3
- package/test/actions/add_contact_urn.test.ts +287 -0
- package/test/actions/send_broadcast.test.ts +190 -0
- package/test/actions/send_email.test.ts +17 -23
- package/test/actions/send_msg.test.ts +39 -15
- package/test/actions/start_session.test.ts +151 -0
- package/test/nodes/split_by_airtime.test.ts +673 -0
- package/test/nodes/split_by_contact_field.test.ts +451 -0
- package/test/nodes/split_by_expression.test.ts +751 -0
- package/test/nodes/split_by_random.test.ts +3 -3
- package/test/nodes/split_by_resthook.test.ts +398 -0
- package/test/nodes/split_by_run_result.test.ts +1109 -0
- package/test/nodes/split_by_scheme.test.ts +486 -0
- package/test/nodes/split_by_subflow.test.ts +381 -0
- package/test/nodes/wait_for_digits.test.ts +2 -2
- package/test/nodes/wait_for_response.test.ts +2 -1
- package/test/temba-action-drag-between-nodes.test.ts +301 -0
- package/test/temba-canvas-menu.test.ts +156 -0
- package/test/temba-flow-editor-node.test.ts +102 -2
- package/test/temba-flow-editor.test.ts +7 -8
- package/test/temba-node-editor.test.ts +3 -1
- package/test/temba-node-type-selector.test.ts +152 -0
- package/test/temba-omnibox.test.ts +2 -1
- package/test/temba-sortable-list.test.ts +69 -0
- package/test/temba-utils-index.test.ts +0 -35
- package/test/utils.test.ts +2 -0
- package/test-assets/contacts/history.json +14 -20
- package/web-dev-server.config.mjs +3 -1
- package/out-tsc/src/flow/actions/call_classifier.js +0 -11
- package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
- package/out-tsc/src/flow/actions/call_resthook.js +0 -11
- package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
- package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
- package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
- package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
- package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
- package/src/flow/actions/call_classifier.ts +0 -12
- package/src/flow/actions/call_resthook.ts +0 -12
- package/src/flow/actions/split_by_expression_example.ts +0 -88
- package/src/flow/actions/transfer_airtime.ts +0 -12
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { split_by_subflow } from '../../src/flow/nodes/split_by_subflow';
|
|
3
|
+
import { Node } from '../../src/store/flow-definition';
|
|
4
|
+
import { NodeTest } from '../NodeHelper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for the split_by_subflow node configuration.
|
|
8
|
+
*/
|
|
9
|
+
describe('split_by_subflow node config', () => {
|
|
10
|
+
const helper = new NodeTest(split_by_subflow, 'split_by_subflow');
|
|
11
|
+
|
|
12
|
+
describe('basic properties', () => {
|
|
13
|
+
helper.testBasicProperties();
|
|
14
|
+
|
|
15
|
+
it('has correct name', () => {
|
|
16
|
+
expect(split_by_subflow.name).to.equal('Enter a Flow');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('has correct type', () => {
|
|
20
|
+
expect(split_by_subflow.type).to.equal('split_by_subflow');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('has showAsAction set to true', () => {
|
|
24
|
+
expect(split_by_subflow.showAsAction).to.be.true;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('has form configuration', () => {
|
|
28
|
+
expect(split_by_subflow.form).to.exist;
|
|
29
|
+
expect(split_by_subflow.form.flow).to.exist;
|
|
30
|
+
expect(split_by_subflow.form.flow.type).to.equal('select');
|
|
31
|
+
expect(split_by_subflow.form.flow.required).to.be.true;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('has layout configuration', () => {
|
|
35
|
+
expect(split_by_subflow.layout).to.exist;
|
|
36
|
+
expect(split_by_subflow.layout).to.deep.equal(['flow']);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('toFormData', () => {
|
|
41
|
+
it('extracts data from node with enter_flow action', () => {
|
|
42
|
+
const node: Node = {
|
|
43
|
+
uuid: 'test-node',
|
|
44
|
+
actions: [
|
|
45
|
+
{
|
|
46
|
+
uuid: 'test-action',
|
|
47
|
+
type: 'enter_flow',
|
|
48
|
+
flow: { uuid: 'flow-123', name: 'Registration Flow' }
|
|
49
|
+
} as any
|
|
50
|
+
],
|
|
51
|
+
router: {
|
|
52
|
+
type: 'switch',
|
|
53
|
+
operand: '@child.status',
|
|
54
|
+
cases: [
|
|
55
|
+
{
|
|
56
|
+
uuid: 'case-1',
|
|
57
|
+
type: 'has_only_text',
|
|
58
|
+
arguments: ['completed'],
|
|
59
|
+
category_uuid: 'cat-1'
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
categories: [
|
|
63
|
+
{ uuid: 'cat-1', name: 'Complete', exit_uuid: 'exit-1' },
|
|
64
|
+
{ uuid: 'cat-2', name: 'Expired', exit_uuid: 'exit-2' }
|
|
65
|
+
],
|
|
66
|
+
default_category_uuid: 'cat-2'
|
|
67
|
+
},
|
|
68
|
+
exits: [
|
|
69
|
+
{ uuid: 'exit-1', destination_uuid: null },
|
|
70
|
+
{ uuid: 'exit-2', destination_uuid: null }
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const formData = split_by_subflow.toFormData(node);
|
|
75
|
+
|
|
76
|
+
expect(formData.uuid).to.equal('test-node');
|
|
77
|
+
expect(formData.flow).to.be.an('array');
|
|
78
|
+
expect(formData.flow).to.have.lengthOf(1);
|
|
79
|
+
expect(formData.flow[0]).to.deep.equal({
|
|
80
|
+
uuid: 'flow-123',
|
|
81
|
+
name: 'Registration Flow'
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handles empty node', () => {
|
|
86
|
+
const node: Node = {
|
|
87
|
+
uuid: 'test-node',
|
|
88
|
+
actions: [],
|
|
89
|
+
exits: []
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const formData = split_by_subflow.toFormData(node);
|
|
93
|
+
|
|
94
|
+
expect(formData.uuid).to.equal('test-node');
|
|
95
|
+
expect(formData.flow).to.be.an('array');
|
|
96
|
+
expect(formData.flow).to.have.lengthOf(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('fromFormData', () => {
|
|
101
|
+
it('creates node with enter_flow action and router from form data', () => {
|
|
102
|
+
const formData = {
|
|
103
|
+
uuid: 'test-node',
|
|
104
|
+
flow: [{ uuid: 'flow-123', name: 'Registration Flow' }]
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const originalNode: Node = {
|
|
108
|
+
uuid: 'test-node',
|
|
109
|
+
actions: [],
|
|
110
|
+
exits: []
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const node = split_by_subflow.fromFormData(formData, originalNode);
|
|
114
|
+
|
|
115
|
+
// Check node structure
|
|
116
|
+
expect(node.uuid).to.equal('test-node');
|
|
117
|
+
expect(node.actions).to.have.length(1);
|
|
118
|
+
expect(node.actions[0].type).to.equal('enter_flow');
|
|
119
|
+
|
|
120
|
+
// Check enter_flow action has properly formatted flow
|
|
121
|
+
const enterFlowAction = node.actions[0] as any;
|
|
122
|
+
expect(enterFlowAction.flow).to.exist;
|
|
123
|
+
expect(enterFlowAction.flow.uuid).to.equal('flow-123');
|
|
124
|
+
expect(enterFlowAction.flow.name).to.equal('Registration Flow');
|
|
125
|
+
|
|
126
|
+
// Check router
|
|
127
|
+
expect(node.router).to.exist;
|
|
128
|
+
expect(node.router.type).to.equal('switch');
|
|
129
|
+
expect(node.router.operand).to.equal('@child.status');
|
|
130
|
+
expect(node.router.categories).to.have.length(2);
|
|
131
|
+
expect(node.router.categories[0].name).to.equal('Complete');
|
|
132
|
+
expect(node.router.categories[1].name).to.equal('Expired');
|
|
133
|
+
|
|
134
|
+
// Check exits
|
|
135
|
+
expect(node.exits).to.have.length(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('handles empty flow selection', () => {
|
|
139
|
+
const formData = {
|
|
140
|
+
uuid: 'test-node',
|
|
141
|
+
flow: []
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const originalNode: Node = {
|
|
145
|
+
uuid: 'test-node',
|
|
146
|
+
actions: [],
|
|
147
|
+
exits: []
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const node = split_by_subflow.fromFormData(formData, originalNode);
|
|
151
|
+
|
|
152
|
+
// Should still create the node structure
|
|
153
|
+
expect(node.actions).to.have.length(1);
|
|
154
|
+
const enterFlowAction = node.actions[0] as any;
|
|
155
|
+
expect(enterFlowAction.flow.uuid).to.equal('');
|
|
156
|
+
expect(enterFlowAction.flow.name).to.equal('');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('preserves UUIDs when editing existing node', () => {
|
|
160
|
+
const existingNode: Node = {
|
|
161
|
+
uuid: 'test-node',
|
|
162
|
+
actions: [
|
|
163
|
+
{
|
|
164
|
+
uuid: 'existing-action-uuid',
|
|
165
|
+
type: 'enter_flow',
|
|
166
|
+
flow: { uuid: 'flow-old', name: 'Old Flow' }
|
|
167
|
+
} as any
|
|
168
|
+
],
|
|
169
|
+
router: {
|
|
170
|
+
type: 'switch',
|
|
171
|
+
operand: '@child.status',
|
|
172
|
+
cases: [
|
|
173
|
+
{
|
|
174
|
+
uuid: 'existing-case-uuid',
|
|
175
|
+
type: 'has_only_text',
|
|
176
|
+
arguments: ['completed'],
|
|
177
|
+
category_uuid: 'existing-cat-1'
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
categories: [
|
|
181
|
+
{
|
|
182
|
+
uuid: 'existing-cat-1',
|
|
183
|
+
name: 'Complete',
|
|
184
|
+
exit_uuid: 'existing-exit-1'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
uuid: 'existing-cat-2',
|
|
188
|
+
name: 'Expired',
|
|
189
|
+
exit_uuid: 'existing-exit-2'
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
default_category_uuid: 'existing-cat-2'
|
|
193
|
+
},
|
|
194
|
+
exits: [
|
|
195
|
+
{ uuid: 'existing-exit-1', destination_uuid: null },
|
|
196
|
+
{ uuid: 'existing-exit-2', destination_uuid: null }
|
|
197
|
+
]
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const formData = {
|
|
201
|
+
uuid: 'test-node',
|
|
202
|
+
flow: [{ uuid: 'flow-new', name: 'New Flow' }]
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const node = split_by_subflow.fromFormData(formData, existingNode);
|
|
206
|
+
|
|
207
|
+
// Should preserve action UUID
|
|
208
|
+
expect(node.actions[0].uuid).to.equal('existing-action-uuid');
|
|
209
|
+
|
|
210
|
+
// Should preserve router structure UUIDs
|
|
211
|
+
expect(node.router.categories[0].uuid).to.equal('existing-cat-1');
|
|
212
|
+
expect(node.router.categories[1].uuid).to.equal('existing-cat-2');
|
|
213
|
+
expect(node.exits[0].uuid).to.equal('existing-exit-1');
|
|
214
|
+
expect(node.exits[1].uuid).to.equal('existing-exit-2');
|
|
215
|
+
|
|
216
|
+
// But should update the flow
|
|
217
|
+
const enterFlowAction = node.actions[0] as any;
|
|
218
|
+
expect(enterFlowAction.flow.uuid).to.equal('flow-new');
|
|
219
|
+
expect(enterFlowAction.flow.name).to.equal('New Flow');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('round-trip transformation', () => {
|
|
224
|
+
it('should preserve node structure through toFormData -> fromFormData', () => {
|
|
225
|
+
const originalNode: Node = {
|
|
226
|
+
uuid: 'test-node-uuid',
|
|
227
|
+
actions: [
|
|
228
|
+
{
|
|
229
|
+
uuid: 'action-uuid',
|
|
230
|
+
type: 'enter_flow',
|
|
231
|
+
flow: { uuid: 'flow-456', name: 'My Subflow' }
|
|
232
|
+
} as any
|
|
233
|
+
],
|
|
234
|
+
router: {
|
|
235
|
+
type: 'switch',
|
|
236
|
+
operand: '@child.status',
|
|
237
|
+
cases: [
|
|
238
|
+
{
|
|
239
|
+
uuid: 'case-uuid',
|
|
240
|
+
type: 'has_only_text',
|
|
241
|
+
arguments: ['completed'],
|
|
242
|
+
category_uuid: 'cat-complete'
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
categories: [
|
|
246
|
+
{
|
|
247
|
+
uuid: 'cat-complete',
|
|
248
|
+
name: 'Complete',
|
|
249
|
+
exit_uuid: 'exit-complete'
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
uuid: 'cat-expired',
|
|
253
|
+
name: 'Expired',
|
|
254
|
+
exit_uuid: 'exit-expired'
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
default_category_uuid: 'cat-expired'
|
|
258
|
+
},
|
|
259
|
+
exits: [
|
|
260
|
+
{ uuid: 'exit-complete', destination_uuid: null },
|
|
261
|
+
{ uuid: 'exit-expired', destination_uuid: null }
|
|
262
|
+
]
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Convert to form data
|
|
266
|
+
const formData = split_by_subflow.toFormData(originalNode);
|
|
267
|
+
|
|
268
|
+
// Convert back to node
|
|
269
|
+
const resultNode = split_by_subflow.fromFormData(formData, originalNode);
|
|
270
|
+
|
|
271
|
+
// Should match the original structure
|
|
272
|
+
expect(resultNode).to.deep.equal(originalNode);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('NodeEditor integration', () => {
|
|
277
|
+
it('should properly use node fromFormData when formDataToNode is called', async () => {
|
|
278
|
+
// This test simulates the bug where node.fromFormData should be used
|
|
279
|
+
// to create the entire node (including actions), not action.fromFormData
|
|
280
|
+
|
|
281
|
+
const { fixture, html } = await import('@open-wc/testing');
|
|
282
|
+
await import('../../temba-modules');
|
|
283
|
+
|
|
284
|
+
const originalNode: Node = {
|
|
285
|
+
uuid: 'test-node-uuid',
|
|
286
|
+
actions: [
|
|
287
|
+
{
|
|
288
|
+
uuid: 'action-uuid',
|
|
289
|
+
type: 'enter_flow',
|
|
290
|
+
flow: { uuid: 'flow-old', name: 'Old Flow' }
|
|
291
|
+
} as any
|
|
292
|
+
],
|
|
293
|
+
router: {
|
|
294
|
+
type: 'switch',
|
|
295
|
+
operand: '@child.status',
|
|
296
|
+
cases: [
|
|
297
|
+
{
|
|
298
|
+
uuid: 'case-uuid',
|
|
299
|
+
type: 'has_only_text',
|
|
300
|
+
arguments: ['completed'],
|
|
301
|
+
category_uuid: 'cat-complete'
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
categories: [
|
|
305
|
+
{
|
|
306
|
+
uuid: 'cat-complete',
|
|
307
|
+
name: 'Complete',
|
|
308
|
+
exit_uuid: 'exit-complete'
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
uuid: 'cat-expired',
|
|
312
|
+
name: 'Expired',
|
|
313
|
+
exit_uuid: 'exit-expired'
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
default_category_uuid: 'cat-expired'
|
|
317
|
+
},
|
|
318
|
+
exits: [
|
|
319
|
+
{ uuid: 'exit-complete', destination_uuid: null },
|
|
320
|
+
{ uuid: 'exit-expired', destination_uuid: null }
|
|
321
|
+
]
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const nodeUI = {
|
|
325
|
+
type: 'split_by_subflow',
|
|
326
|
+
position: { left: 50, top: 50 }
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Create the node editor
|
|
330
|
+
const nodeEditor = (await fixture(html`
|
|
331
|
+
<temba-node-editor
|
|
332
|
+
.node=${originalNode}
|
|
333
|
+
.nodeUI=${nodeUI}
|
|
334
|
+
.action=${originalNode.actions[0]}
|
|
335
|
+
.isOpen=${true}
|
|
336
|
+
></temba-node-editor>
|
|
337
|
+
`)) as any;
|
|
338
|
+
|
|
339
|
+
await nodeEditor.updateComplete;
|
|
340
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
341
|
+
await nodeEditor.updateComplete;
|
|
342
|
+
|
|
343
|
+
// Get the initial formData to verify toFormData was called
|
|
344
|
+
const initialFormData = nodeEditor.formData;
|
|
345
|
+
expect(initialFormData.flow).to.exist;
|
|
346
|
+
expect(initialFormData.flow).to.have.lengthOf(1);
|
|
347
|
+
expect(initialFormData.flow[0].uuid).to.equal('flow-old');
|
|
348
|
+
|
|
349
|
+
// Simulate changing the flow selection
|
|
350
|
+
const newFormData = {
|
|
351
|
+
...initialFormData,
|
|
352
|
+
flow: [{ uuid: 'flow-new', name: 'New Flow' }]
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Call formDataToNode directly to test the transformation
|
|
356
|
+
const resultNode = nodeEditor.formDataToNode(newFormData);
|
|
357
|
+
|
|
358
|
+
// Verify that the result node has the new flow properly formatted
|
|
359
|
+
expect(resultNode).to.exist;
|
|
360
|
+
expect(resultNode.actions).to.have.length(1);
|
|
361
|
+
|
|
362
|
+
const enterFlowAction = resultNode.actions[0] as any;
|
|
363
|
+
expect(enterFlowAction.type).to.equal('enter_flow');
|
|
364
|
+
|
|
365
|
+
// This is the key assertion - the flow should be properly formatted
|
|
366
|
+
// as created by node's fromFormData, not by action's fromFormData
|
|
367
|
+
expect(enterFlowAction.flow).to.exist;
|
|
368
|
+
expect(enterFlowAction.flow.uuid).to.equal('flow-new');
|
|
369
|
+
expect(enterFlowAction.flow.name).to.equal('New Flow');
|
|
370
|
+
|
|
371
|
+
// The flow should NOT have extra properties that might come from the select component
|
|
372
|
+
expect(enterFlowAction.flow).to.not.have.property('value');
|
|
373
|
+
|
|
374
|
+
// Verify router structure was also created properly by node's fromFormData
|
|
375
|
+
expect(resultNode.router).to.exist;
|
|
376
|
+
expect(resultNode.router.type).to.equal('switch');
|
|
377
|
+
expect(resultNode.router.categories).to.have.length(2);
|
|
378
|
+
expect(resultNode.exits).to.have.length(2);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -20,8 +20,8 @@ describe('wait_for_digits node config', () => {
|
|
|
20
20
|
expect(wait_for_digits.type).to.equal('wait_for_digits');
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('has correct
|
|
24
|
-
expect(wait_for_digits.
|
|
23
|
+
it('has correct group', () => {
|
|
24
|
+
expect(wait_for_digits.group).to.exist;
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('is a simple node config without form or layout', () => {
|
|
@@ -375,7 +375,8 @@ describe('wait_for_response node config', () => {
|
|
|
375
375
|
const result = wait_for_response.fromFormData!(formData, originalNode);
|
|
376
376
|
|
|
377
377
|
expect(result.uuid).to.equal('test-node');
|
|
378
|
-
|
|
378
|
+
// When no result_name is provided, it should not be set
|
|
379
|
+
expect(result.router?.result_name).to.be.undefined;
|
|
379
380
|
});
|
|
380
381
|
|
|
381
382
|
it('handles operators with no operands correctly', () => {
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
2
|
+
import { CanvasNode } from '../src/flow/CanvasNode';
|
|
3
|
+
import { Node, NodeUI, SendMsg } from '../src/store/flow-definition';
|
|
4
|
+
import '../temba-modules';
|
|
5
|
+
|
|
6
|
+
describe('Drag actions between nodes', () => {
|
|
7
|
+
let node1: Node;
|
|
8
|
+
let node1UI: NodeUI;
|
|
9
|
+
let node2: Node;
|
|
10
|
+
let node2UI: NodeUI;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Create test nodes
|
|
14
|
+
node1 = {
|
|
15
|
+
uuid: 'node-1',
|
|
16
|
+
actions: [
|
|
17
|
+
{
|
|
18
|
+
type: 'send_msg',
|
|
19
|
+
uuid: 'action-1',
|
|
20
|
+
text: 'First message',
|
|
21
|
+
quick_replies: []
|
|
22
|
+
} as SendMsg,
|
|
23
|
+
{
|
|
24
|
+
type: 'send_msg',
|
|
25
|
+
uuid: 'action-2',
|
|
26
|
+
text: 'Second message',
|
|
27
|
+
quick_replies: []
|
|
28
|
+
} as SendMsg
|
|
29
|
+
],
|
|
30
|
+
exits: [{ uuid: 'exit-1', destination_uuid: null }]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
node1UI = {
|
|
34
|
+
position: { left: 100, top: 100 },
|
|
35
|
+
type: 'execute_actions',
|
|
36
|
+
config: {}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
node2 = {
|
|
40
|
+
uuid: 'node-2',
|
|
41
|
+
actions: [
|
|
42
|
+
{
|
|
43
|
+
type: 'send_msg',
|
|
44
|
+
uuid: 'action-3',
|
|
45
|
+
text: 'Third message',
|
|
46
|
+
quick_replies: []
|
|
47
|
+
} as SendMsg
|
|
48
|
+
],
|
|
49
|
+
exits: [{ uuid: 'exit-2', destination_uuid: null }]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
node2UI = {
|
|
53
|
+
position: { left: 400, top: 100 },
|
|
54
|
+
type: 'execute_actions',
|
|
55
|
+
config: {}
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should render execute_actions node with sortable list', async () => {
|
|
60
|
+
const node1Element = await fixture<CanvasNode>(html`
|
|
61
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
await node1Element.updateComplete;
|
|
65
|
+
|
|
66
|
+
// Verify node renders with sortable list
|
|
67
|
+
const sortableList = node1Element.querySelector('temba-sortable-list');
|
|
68
|
+
expect(sortableList).to.exist;
|
|
69
|
+
|
|
70
|
+
// Verify actions are rendered
|
|
71
|
+
const actions = node1Element.querySelectorAll('.action.sortable');
|
|
72
|
+
expect(actions.length).to.equal(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should show placeholder in target node during drag', async () => {
|
|
76
|
+
const node2Element = await fixture<CanvasNode>(html`
|
|
77
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
await node2Element.updateComplete;
|
|
81
|
+
|
|
82
|
+
// Simulate action-drag-over event from Editor
|
|
83
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
84
|
+
detail: {
|
|
85
|
+
action: node1.actions[0],
|
|
86
|
+
sourceNodeUuid: 'node-1',
|
|
87
|
+
actionIndex: 0,
|
|
88
|
+
mouseY: 150
|
|
89
|
+
},
|
|
90
|
+
bubbles: false
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
94
|
+
await node2Element.updateComplete;
|
|
95
|
+
|
|
96
|
+
// Check that placeholder is rendered
|
|
97
|
+
const placeholder = node2Element.querySelector('.drop-placeholder');
|
|
98
|
+
expect(placeholder).to.exist;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle drag-over event and store external drag info', async () => {
|
|
102
|
+
const node2Element = await fixture<CanvasNode>(html`
|
|
103
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
104
|
+
`);
|
|
105
|
+
|
|
106
|
+
await node2Element.updateComplete;
|
|
107
|
+
|
|
108
|
+
// Simulate drag over
|
|
109
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
110
|
+
detail: {
|
|
111
|
+
action: node1.actions[0],
|
|
112
|
+
sourceNodeUuid: 'node-1',
|
|
113
|
+
actionIndex: 0,
|
|
114
|
+
mouseY: 150
|
|
115
|
+
},
|
|
116
|
+
bubbles: false
|
|
117
|
+
});
|
|
118
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
119
|
+
await node2Element.updateComplete;
|
|
120
|
+
|
|
121
|
+
// Verify that external drag info is stored (check internal state)
|
|
122
|
+
const externalDragInfo = (node2Element as any).externalDragInfo;
|
|
123
|
+
expect(externalDragInfo).to.exist;
|
|
124
|
+
expect(externalDragInfo.action.uuid).to.equal('action-1');
|
|
125
|
+
expect(externalDragInfo.sourceNodeUuid).to.equal('node-1');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should calculate correct drop index based on mouse position', async () => {
|
|
129
|
+
const node2Element = await fixture<CanvasNode>(html`
|
|
130
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
131
|
+
`);
|
|
132
|
+
|
|
133
|
+
await node2Element.updateComplete;
|
|
134
|
+
|
|
135
|
+
// Get action element bounds to calculate positions
|
|
136
|
+
const actionElement = node2Element.querySelector(
|
|
137
|
+
'.action.sortable'
|
|
138
|
+
) as HTMLElement;
|
|
139
|
+
expect(actionElement).to.exist;
|
|
140
|
+
|
|
141
|
+
const rect = actionElement.getBoundingClientRect();
|
|
142
|
+
const topY = rect.top + 5; // Near top of first action
|
|
143
|
+
const bottomY = rect.bottom + 5; // Below first action
|
|
144
|
+
|
|
145
|
+
// Drag over at top
|
|
146
|
+
const dragOverEventTop = new CustomEvent('action-drag-over', {
|
|
147
|
+
detail: {
|
|
148
|
+
action: node1.actions[0],
|
|
149
|
+
sourceNodeUuid: 'node-1',
|
|
150
|
+
actionIndex: 0,
|
|
151
|
+
mouseY: topY
|
|
152
|
+
},
|
|
153
|
+
bubbles: false
|
|
154
|
+
});
|
|
155
|
+
node2Element.dispatchEvent(dragOverEventTop);
|
|
156
|
+
await node2Element.updateComplete;
|
|
157
|
+
|
|
158
|
+
// Check drop index is at beginning
|
|
159
|
+
let externalDragInfo = (node2Element as any).externalDragInfo;
|
|
160
|
+
expect(externalDragInfo.dropIndex).to.equal(0);
|
|
161
|
+
|
|
162
|
+
// Drag over at bottom
|
|
163
|
+
const dragOverEventBottom = new CustomEvent('action-drag-over', {
|
|
164
|
+
detail: {
|
|
165
|
+
action: node1.actions[0],
|
|
166
|
+
sourceNodeUuid: 'node-1',
|
|
167
|
+
actionIndex: 0,
|
|
168
|
+
mouseY: bottomY
|
|
169
|
+
},
|
|
170
|
+
bubbles: false
|
|
171
|
+
});
|
|
172
|
+
node2Element.dispatchEvent(dragOverEventBottom);
|
|
173
|
+
await node2Element.updateComplete;
|
|
174
|
+
|
|
175
|
+
// Check drop index is at end
|
|
176
|
+
externalDragInfo = (node2Element as any).externalDragInfo;
|
|
177
|
+
expect(externalDragInfo.dropIndex).to.equal(1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should not accept drops from the same node', async () => {
|
|
181
|
+
const node1Element = await fixture<CanvasNode>(html`
|
|
182
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
183
|
+
`);
|
|
184
|
+
|
|
185
|
+
await node1Element.updateComplete;
|
|
186
|
+
|
|
187
|
+
// Try to drop action from node-1 onto node-1
|
|
188
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
189
|
+
detail: {
|
|
190
|
+
action: node1.actions[0],
|
|
191
|
+
sourceNodeUuid: 'node-1', // Same as target
|
|
192
|
+
actionIndex: 0,
|
|
193
|
+
mouseY: 150
|
|
194
|
+
},
|
|
195
|
+
bubbles: false
|
|
196
|
+
});
|
|
197
|
+
node1Element.dispatchEvent(dragOverEvent);
|
|
198
|
+
await node1Element.updateComplete;
|
|
199
|
+
|
|
200
|
+
// Verify external drag info was NOT stored (rejected due to same source)
|
|
201
|
+
const externalDragInfo = (node1Element as any).externalDragInfo;
|
|
202
|
+
expect(externalDragInfo).to.be.null;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should clear external drag state and hide placeholder', async () => {
|
|
206
|
+
const node2Element = await fixture<CanvasNode>(html`
|
|
207
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
208
|
+
`);
|
|
209
|
+
|
|
210
|
+
await node2Element.updateComplete;
|
|
211
|
+
|
|
212
|
+
// Simulate drag over
|
|
213
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
214
|
+
detail: {
|
|
215
|
+
action: node1.actions[0],
|
|
216
|
+
sourceNodeUuid: 'node-1',
|
|
217
|
+
actionIndex: 0,
|
|
218
|
+
mouseY: 150
|
|
219
|
+
},
|
|
220
|
+
bubbles: false
|
|
221
|
+
});
|
|
222
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
223
|
+
await node2Element.updateComplete;
|
|
224
|
+
|
|
225
|
+
// Verify placeholder exists
|
|
226
|
+
let placeholder = node2Element.querySelector('.drop-placeholder');
|
|
227
|
+
expect(placeholder).to.exist;
|
|
228
|
+
|
|
229
|
+
// Clear the external drag state (simulating drag leaving the node)
|
|
230
|
+
(node2Element as any).externalDragInfo = null;
|
|
231
|
+
// Trigger re-render
|
|
232
|
+
node2Element.requestUpdate();
|
|
233
|
+
await node2Element.updateComplete;
|
|
234
|
+
|
|
235
|
+
// Verify placeholder is gone
|
|
236
|
+
placeholder = node2Element.querySelector('.drop-placeholder');
|
|
237
|
+
expect(placeholder).to.not.exist;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should have sortable list for internal drag support', async () => {
|
|
241
|
+
const node1Element = await fixture<CanvasNode>(html`
|
|
242
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
243
|
+
`);
|
|
244
|
+
|
|
245
|
+
await node1Element.updateComplete;
|
|
246
|
+
|
|
247
|
+
// Verify sortable list exists for internal drag (reordering within same node)
|
|
248
|
+
const sortableList = node1Element.querySelector('temba-sortable-list');
|
|
249
|
+
expect(sortableList).to.exist;
|
|
250
|
+
|
|
251
|
+
// Verify it has external drag enabled
|
|
252
|
+
expect(sortableList.hasAttribute('externaldrag')).to.be.true;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should only accept drops on execute_actions nodes', async () => {
|
|
256
|
+
const routerNodeUI: NodeUI = {
|
|
257
|
+
position: { left: 100, top: 100 },
|
|
258
|
+
type: 'split_by_expression', // Not execute_actions
|
|
259
|
+
config: {}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const routerNode: Node = {
|
|
263
|
+
uuid: 'router-node',
|
|
264
|
+
actions: [],
|
|
265
|
+
exits: [{ uuid: 'exit-1', destination_uuid: null }],
|
|
266
|
+
router: {
|
|
267
|
+
type: 'switch',
|
|
268
|
+
operand: '@input',
|
|
269
|
+
cases: [],
|
|
270
|
+
categories: [],
|
|
271
|
+
default_category_uuid: 'cat-1'
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const routerElement = await fixture<CanvasNode>(html`
|
|
276
|
+
<temba-flow-node
|
|
277
|
+
.node=${routerNode}
|
|
278
|
+
.ui=${routerNodeUI}
|
|
279
|
+
></temba-flow-node>
|
|
280
|
+
`);
|
|
281
|
+
|
|
282
|
+
await routerElement.updateComplete;
|
|
283
|
+
|
|
284
|
+
// Try to drag over a non-execute_actions node
|
|
285
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
286
|
+
detail: {
|
|
287
|
+
action: node1.actions[0],
|
|
288
|
+
sourceNodeUuid: 'node-1',
|
|
289
|
+
actionIndex: 0,
|
|
290
|
+
mouseY: 150
|
|
291
|
+
},
|
|
292
|
+
bubbles: false
|
|
293
|
+
});
|
|
294
|
+
routerElement.dispatchEvent(dragOverEvent);
|
|
295
|
+
await routerElement.updateComplete;
|
|
296
|
+
|
|
297
|
+
// Verify external drag info was NOT stored (rejected due to wrong node type)
|
|
298
|
+
const externalDragInfo = (routerElement as any).externalDragInfo;
|
|
299
|
+
expect(externalDragInfo).to.be.null;
|
|
300
|
+
});
|
|
301
|
+
});
|