@nyaruka/temba-components 0.131.1 → 0.131.3
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 +75 -1
- package/demo/components/floating-tabs/example.html +400 -0
- package/demo/components/flow/index.html +1 -1
- package/demo/data/flows/food-order.json +2 -2
- package/demo/data/flows/sample-flow.json +113 -125
- package/demo/data/flows/voicemail.json +613 -0
- package/demo/index.html +6 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1773 -662
- 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/display/FloatingTab.js +167 -0
- package/out-tsc/src/display/FloatingTab.js.map +1 -0
- package/out-tsc/src/display/ProgressBar.js +22 -2
- package/out-tsc/src/display/ProgressBar.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 +489 -47
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +1417 -67
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +479 -112
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +540 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
- package/out-tsc/src/flow/StickyNote.js +12 -3
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +4 -3
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +63 -4
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +4 -3
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +3 -2
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +7 -5
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +3 -2
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +3 -2
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +77 -23
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +5 -5
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +101 -21
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +6 -9
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +20 -20
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +3 -2
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +3 -12
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +3 -2
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +4 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +181 -6
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -23
- 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 +71 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +211 -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 +152 -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 +73 -2
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +18 -10
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +8 -0
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm.js +11 -3
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +10 -4
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +113 -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 +211 -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 +158 -2
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +13 -5
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +38 -568
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +86 -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/layout/FloatingWindow.js +346 -0
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
- 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 +6 -25
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +120 -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 +8 -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-floating-tab.test.js +91 -0
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
- package/out-tsc/test/temba-floating-window.test.js +301 -0
- package/out-tsc/test/temba-floating-window.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +202 -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-localization.test.js +471 -0
- package/out-tsc/test/temba-localization.test.js.map +1 -0
- 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 +265 -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 +20 -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/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hidden.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/floating-window/chromeless.png +0 -0
- package/screenshots/truth/floating-window/custom-size.png +0 -0
- package/screenshots/truth/floating-window/default.png +0 -0
- package/screenshots/truth/floating-window/with-header.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/display/FloatingTab.ts +174 -0
- package/src/display/ProgressBar.ts +22 -2
- package/src/events.ts +2 -8
- package/src/flow/CanvasMenu.ts +217 -0
- package/src/flow/CanvasNode.ts +596 -40
- package/src/flow/Editor.ts +1721 -45
- package/src/flow/NodeEditor.ts +621 -144
- package/src/flow/NodeTypeSelector.ts +636 -0
- package/src/flow/StickyNote.ts +12 -3
- package/src/flow/actions/add_contact_groups.ts +5 -4
- package/src/flow/actions/add_contact_urn.ts +78 -4
- package/src/flow/actions/add_input_labels.ts +5 -4
- package/src/flow/actions/play_audio.ts +3 -2
- package/src/flow/actions/remove_contact_groups.ts +16 -6
- package/src/flow/actions/request_optin.ts +3 -2
- package/src/flow/actions/say_msg.ts +3 -2
- package/src/flow/actions/send_broadcast.ts +86 -23
- package/src/flow/actions/send_email.ts +12 -6
- package/src/flow/actions/send_msg.ts +155 -34
- package/src/flow/actions/set_contact_channel.ts +6 -11
- package/src/flow/actions/set_contact_field.ts +21 -25
- package/src/flow/actions/set_contact_language.ts +11 -4
- package/src/flow/actions/set_contact_name.ts +4 -15
- package/src/flow/actions/set_contact_status.ts +4 -3
- package/src/flow/actions/set_run_result.ts +5 -4
- package/src/flow/actions/start_session.ts +210 -6
- package/src/flow/config.ts +11 -23
- package/src/flow/currencies.ts +51 -0
- package/src/flow/nodes/shared-rules.ts +301 -0
- package/src/flow/nodes/shared.ts +87 -0
- package/src/flow/nodes/split_by_airtime.ts +255 -5
- package/src/flow/nodes/split_by_contact_field.ts +195 -3
- package/src/flow/nodes/split_by_expression.ts +104 -2
- package/src/flow/nodes/split_by_groups.ts +26 -11
- package/src/flow/nodes/split_by_intent.ts +8 -0
- package/src/flow/nodes/split_by_llm.ts +22 -4
- package/src/flow/nodes/split_by_llm_categorize.ts +22 -5
- package/src/flow/nodes/split_by_random.ts +16 -6
- package/src/flow/nodes/split_by_resthook.ts +140 -0
- package/src/flow/nodes/split_by_run_result.ts +259 -3
- package/src/flow/nodes/split_by_scheme.ts +202 -2
- package/src/flow/nodes/split_by_subflow.ts +17 -5
- package/src/flow/nodes/split_by_ticket.ts +15 -4
- package/src/flow/nodes/split_by_webhook.ts +17 -6
- package/src/flow/nodes/wait_for_digits.ts +3 -2
- package/src/flow/nodes/wait_for_menu.ts +3 -2
- package/src/flow/nodes/wait_for_response.ts +59 -680
- package/src/flow/types.ts +156 -23
- package/src/flow/utils.ts +108 -14
- package/src/form/FieldRenderer.ts +2 -4
- package/src/interfaces.ts +3 -0
- package/src/layout/FloatingWindow.ts +386 -0
- package/src/list/SortableList.ts +109 -34
- package/src/live/ContactChat.ts +7 -25
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/store/AppState.ts +173 -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/llms.json +18 -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 +8 -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-floating-tab.test.ts +110 -0
- package/test/temba-floating-window.test.ts +477 -0
- package/test/temba-flow-editor-node.test.ts +246 -2
- package/test/temba-flow-editor.test.ts +7 -8
- package/test/temba-localization.test.ts +611 -0
- package/test/temba-node-editor.test.ts +3 -1
- package/test/temba-node-type-selector.test.ts +355 -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 +22 -0
- package/test-assets/contacts/history.json +14 -21
- package/test-assets/select/llms.json +2 -2
- package/web-dev-server.config.mjs +49 -1
- package/web-test-runner.config.mjs +0 -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/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_video.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
- package/src/flow/nodes/wait_for_audio.ts +0 -7
- package/src/flow/nodes/wait_for_image.ts +0 -7
- package/src/flow/nodes/wait_for_location.ts +0 -7
- package/src/flow/nodes/wait_for_video.ts +0 -7
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
2
|
+
import '../temba-modules';
|
|
3
|
+
describe('Drag actions between nodes', () => {
|
|
4
|
+
let node1;
|
|
5
|
+
let node1UI;
|
|
6
|
+
let node2;
|
|
7
|
+
let node2UI;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Create test nodes
|
|
10
|
+
node1 = {
|
|
11
|
+
uuid: 'node-1',
|
|
12
|
+
actions: [
|
|
13
|
+
{
|
|
14
|
+
type: 'send_msg',
|
|
15
|
+
uuid: 'action-1',
|
|
16
|
+
text: 'First message',
|
|
17
|
+
quick_replies: []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'send_msg',
|
|
21
|
+
uuid: 'action-2',
|
|
22
|
+
text: 'Second message',
|
|
23
|
+
quick_replies: []
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
exits: [{ uuid: 'exit-1', destination_uuid: null }]
|
|
27
|
+
};
|
|
28
|
+
node1UI = {
|
|
29
|
+
position: { left: 100, top: 100 },
|
|
30
|
+
type: 'execute_actions',
|
|
31
|
+
config: {}
|
|
32
|
+
};
|
|
33
|
+
node2 = {
|
|
34
|
+
uuid: 'node-2',
|
|
35
|
+
actions: [
|
|
36
|
+
{
|
|
37
|
+
type: 'send_msg',
|
|
38
|
+
uuid: 'action-3',
|
|
39
|
+
text: 'Third message',
|
|
40
|
+
quick_replies: []
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
exits: [{ uuid: 'exit-2', destination_uuid: null }]
|
|
44
|
+
};
|
|
45
|
+
node2UI = {
|
|
46
|
+
position: { left: 400, top: 100 },
|
|
47
|
+
type: 'execute_actions',
|
|
48
|
+
config: {}
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
it('should render execute_actions node with sortable list', async () => {
|
|
52
|
+
const node1Element = await fixture(html `
|
|
53
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
54
|
+
`);
|
|
55
|
+
await node1Element.updateComplete;
|
|
56
|
+
// Verify node renders with sortable list
|
|
57
|
+
const sortableList = node1Element.querySelector('temba-sortable-list');
|
|
58
|
+
expect(sortableList).to.exist;
|
|
59
|
+
// Verify actions are rendered
|
|
60
|
+
const actions = node1Element.querySelectorAll('.action.sortable');
|
|
61
|
+
expect(actions.length).to.equal(2);
|
|
62
|
+
});
|
|
63
|
+
it('should show placeholder in target node during drag', async () => {
|
|
64
|
+
const node2Element = await fixture(html `
|
|
65
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
66
|
+
`);
|
|
67
|
+
await node2Element.updateComplete;
|
|
68
|
+
// Simulate action-drag-over event from Editor
|
|
69
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
70
|
+
detail: {
|
|
71
|
+
action: node1.actions[0],
|
|
72
|
+
sourceNodeUuid: 'node-1',
|
|
73
|
+
actionIndex: 0,
|
|
74
|
+
mouseY: 150
|
|
75
|
+
},
|
|
76
|
+
bubbles: false
|
|
77
|
+
});
|
|
78
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
79
|
+
await node2Element.updateComplete;
|
|
80
|
+
// Check that placeholder is rendered
|
|
81
|
+
const placeholder = node2Element.querySelector('.drop-placeholder');
|
|
82
|
+
expect(placeholder).to.exist;
|
|
83
|
+
});
|
|
84
|
+
it('should handle drag-over event and store external drag info', async () => {
|
|
85
|
+
const node2Element = await fixture(html `
|
|
86
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
87
|
+
`);
|
|
88
|
+
await node2Element.updateComplete;
|
|
89
|
+
// Simulate drag over
|
|
90
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
91
|
+
detail: {
|
|
92
|
+
action: node1.actions[0],
|
|
93
|
+
sourceNodeUuid: 'node-1',
|
|
94
|
+
actionIndex: 0,
|
|
95
|
+
mouseY: 150
|
|
96
|
+
},
|
|
97
|
+
bubbles: false
|
|
98
|
+
});
|
|
99
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
100
|
+
await node2Element.updateComplete;
|
|
101
|
+
// Verify that external drag info is stored (check internal state)
|
|
102
|
+
const externalDragInfo = node2Element.externalDragInfo;
|
|
103
|
+
expect(externalDragInfo).to.exist;
|
|
104
|
+
expect(externalDragInfo.action.uuid).to.equal('action-1');
|
|
105
|
+
expect(externalDragInfo.sourceNodeUuid).to.equal('node-1');
|
|
106
|
+
});
|
|
107
|
+
it('should calculate correct drop index based on mouse position', async () => {
|
|
108
|
+
const node2Element = await fixture(html `
|
|
109
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
110
|
+
`);
|
|
111
|
+
await node2Element.updateComplete;
|
|
112
|
+
// Get action element bounds to calculate positions
|
|
113
|
+
const actionElement = node2Element.querySelector('.action.sortable');
|
|
114
|
+
expect(actionElement).to.exist;
|
|
115
|
+
const rect = actionElement.getBoundingClientRect();
|
|
116
|
+
const topY = rect.top + 5; // Near top of first action
|
|
117
|
+
const bottomY = rect.bottom + 5; // Below first action
|
|
118
|
+
// Drag over at top
|
|
119
|
+
const dragOverEventTop = new CustomEvent('action-drag-over', {
|
|
120
|
+
detail: {
|
|
121
|
+
action: node1.actions[0],
|
|
122
|
+
sourceNodeUuid: 'node-1',
|
|
123
|
+
actionIndex: 0,
|
|
124
|
+
mouseY: topY
|
|
125
|
+
},
|
|
126
|
+
bubbles: false
|
|
127
|
+
});
|
|
128
|
+
node2Element.dispatchEvent(dragOverEventTop);
|
|
129
|
+
await node2Element.updateComplete;
|
|
130
|
+
// Check drop index is at beginning
|
|
131
|
+
let externalDragInfo = node2Element.externalDragInfo;
|
|
132
|
+
expect(externalDragInfo.dropIndex).to.equal(0);
|
|
133
|
+
// Drag over at bottom
|
|
134
|
+
const dragOverEventBottom = new CustomEvent('action-drag-over', {
|
|
135
|
+
detail: {
|
|
136
|
+
action: node1.actions[0],
|
|
137
|
+
sourceNodeUuid: 'node-1',
|
|
138
|
+
actionIndex: 0,
|
|
139
|
+
mouseY: bottomY
|
|
140
|
+
},
|
|
141
|
+
bubbles: false
|
|
142
|
+
});
|
|
143
|
+
node2Element.dispatchEvent(dragOverEventBottom);
|
|
144
|
+
await node2Element.updateComplete;
|
|
145
|
+
// Check drop index is at end
|
|
146
|
+
externalDragInfo = node2Element.externalDragInfo;
|
|
147
|
+
expect(externalDragInfo.dropIndex).to.equal(1);
|
|
148
|
+
});
|
|
149
|
+
it('should not accept drops from the same node', async () => {
|
|
150
|
+
const node1Element = await fixture(html `
|
|
151
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
152
|
+
`);
|
|
153
|
+
await node1Element.updateComplete;
|
|
154
|
+
// Try to drop action from node-1 onto node-1
|
|
155
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
156
|
+
detail: {
|
|
157
|
+
action: node1.actions[0],
|
|
158
|
+
sourceNodeUuid: 'node-1', // Same as target
|
|
159
|
+
actionIndex: 0,
|
|
160
|
+
mouseY: 150
|
|
161
|
+
},
|
|
162
|
+
bubbles: false
|
|
163
|
+
});
|
|
164
|
+
node1Element.dispatchEvent(dragOverEvent);
|
|
165
|
+
await node1Element.updateComplete;
|
|
166
|
+
// Verify external drag info was NOT stored (rejected due to same source)
|
|
167
|
+
const externalDragInfo = node1Element.externalDragInfo;
|
|
168
|
+
expect(externalDragInfo).to.be.null;
|
|
169
|
+
});
|
|
170
|
+
it('should clear external drag state and hide placeholder', async () => {
|
|
171
|
+
const node2Element = await fixture(html `
|
|
172
|
+
<temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
|
|
173
|
+
`);
|
|
174
|
+
await node2Element.updateComplete;
|
|
175
|
+
// Simulate drag over
|
|
176
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
177
|
+
detail: {
|
|
178
|
+
action: node1.actions[0],
|
|
179
|
+
sourceNodeUuid: 'node-1',
|
|
180
|
+
actionIndex: 0,
|
|
181
|
+
mouseY: 150
|
|
182
|
+
},
|
|
183
|
+
bubbles: false
|
|
184
|
+
});
|
|
185
|
+
node2Element.dispatchEvent(dragOverEvent);
|
|
186
|
+
await node2Element.updateComplete;
|
|
187
|
+
// Verify placeholder exists
|
|
188
|
+
let placeholder = node2Element.querySelector('.drop-placeholder');
|
|
189
|
+
expect(placeholder).to.exist;
|
|
190
|
+
// Clear the external drag state (simulating drag leaving the node)
|
|
191
|
+
node2Element.externalDragInfo = null;
|
|
192
|
+
// Trigger re-render
|
|
193
|
+
node2Element.requestUpdate();
|
|
194
|
+
await node2Element.updateComplete;
|
|
195
|
+
// Verify placeholder is gone
|
|
196
|
+
placeholder = node2Element.querySelector('.drop-placeholder');
|
|
197
|
+
expect(placeholder).to.not.exist;
|
|
198
|
+
});
|
|
199
|
+
it('should have sortable list for internal drag support', async () => {
|
|
200
|
+
const node1Element = await fixture(html `
|
|
201
|
+
<temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
|
|
202
|
+
`);
|
|
203
|
+
await node1Element.updateComplete;
|
|
204
|
+
// Verify sortable list exists for internal drag (reordering within same node)
|
|
205
|
+
const sortableList = node1Element.querySelector('temba-sortable-list');
|
|
206
|
+
expect(sortableList).to.exist;
|
|
207
|
+
// Verify it has external drag enabled
|
|
208
|
+
expect(sortableList.hasAttribute('externaldrag')).to.be.true;
|
|
209
|
+
});
|
|
210
|
+
it('should only accept drops on execute_actions nodes', async () => {
|
|
211
|
+
const routerNodeUI = {
|
|
212
|
+
position: { left: 100, top: 100 },
|
|
213
|
+
type: 'split_by_expression', // Not execute_actions
|
|
214
|
+
config: {}
|
|
215
|
+
};
|
|
216
|
+
const routerNode = {
|
|
217
|
+
uuid: 'router-node',
|
|
218
|
+
actions: [],
|
|
219
|
+
exits: [{ uuid: 'exit-1', destination_uuid: null }],
|
|
220
|
+
router: {
|
|
221
|
+
type: 'switch',
|
|
222
|
+
operand: '@input',
|
|
223
|
+
cases: [],
|
|
224
|
+
categories: [],
|
|
225
|
+
default_category_uuid: 'cat-1'
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const routerElement = await fixture(html `
|
|
229
|
+
<temba-flow-node
|
|
230
|
+
.node=${routerNode}
|
|
231
|
+
.ui=${routerNodeUI}
|
|
232
|
+
></temba-flow-node>
|
|
233
|
+
`);
|
|
234
|
+
await routerElement.updateComplete;
|
|
235
|
+
// Try to drag over a non-execute_actions node
|
|
236
|
+
const dragOverEvent = new CustomEvent('action-drag-over', {
|
|
237
|
+
detail: {
|
|
238
|
+
action: node1.actions[0],
|
|
239
|
+
sourceNodeUuid: 'node-1',
|
|
240
|
+
actionIndex: 0,
|
|
241
|
+
mouseY: 150
|
|
242
|
+
},
|
|
243
|
+
bubbles: false
|
|
244
|
+
});
|
|
245
|
+
routerElement.dispatchEvent(dragOverEvent);
|
|
246
|
+
await routerElement.updateComplete;
|
|
247
|
+
// Verify external drag info was NOT stored (rejected due to wrong node type)
|
|
248
|
+
const externalDragInfo = routerElement.externalDragInfo;
|
|
249
|
+
expect(externalDragInfo).to.be.null;
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
//# sourceMappingURL=temba-action-drag-between-nodes.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temba-action-drag-between-nodes.test.js","sourceRoot":"","sources":["../../test/temba-action-drag-between-nodes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html } from '@open-wc/testing';\nimport { CanvasNode } from '../src/flow/CanvasNode';\nimport { Node, NodeUI, SendMsg } from '../src/store/flow-definition';\nimport '../temba-modules';\n\ndescribe('Drag actions between nodes', () => {\n let node1: Node;\n let node1UI: NodeUI;\n let node2: Node;\n let node2UI: NodeUI;\n\n beforeEach(() => {\n // Create test nodes\n node1 = {\n uuid: 'node-1',\n actions: [\n {\n type: 'send_msg',\n uuid: 'action-1',\n text: 'First message',\n quick_replies: []\n } as SendMsg,\n {\n type: 'send_msg',\n uuid: 'action-2',\n text: 'Second message',\n quick_replies: []\n } as SendMsg\n ],\n exits: [{ uuid: 'exit-1', destination_uuid: null }]\n };\n\n node1UI = {\n position: { left: 100, top: 100 },\n type: 'execute_actions',\n config: {}\n };\n\n node2 = {\n uuid: 'node-2',\n actions: [\n {\n type: 'send_msg',\n uuid: 'action-3',\n text: 'Third message',\n quick_replies: []\n } as SendMsg\n ],\n exits: [{ uuid: 'exit-2', destination_uuid: null }]\n };\n\n node2UI = {\n position: { left: 400, top: 100 },\n type: 'execute_actions',\n config: {}\n };\n });\n\n it('should render execute_actions node with sortable list', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Verify node renders with sortable list\n const sortableList = node1Element.querySelector('temba-sortable-list');\n expect(sortableList).to.exist;\n\n // Verify actions are rendered\n const actions = node1Element.querySelectorAll('.action.sortable');\n expect(actions.length).to.equal(2);\n });\n\n it('should show placeholder in target node during drag', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate action-drag-over event from Editor\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Check that placeholder is rendered\n const placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.exist;\n });\n\n it('should handle drag-over event and store external drag info', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate drag over\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Verify that external drag info is stored (check internal state)\n const externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo).to.exist;\n expect(externalDragInfo.action.uuid).to.equal('action-1');\n expect(externalDragInfo.sourceNodeUuid).to.equal('node-1');\n });\n\n it('should calculate correct drop index based on mouse position', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Get action element bounds to calculate positions\n const actionElement = node2Element.querySelector(\n '.action.sortable'\n ) as HTMLElement;\n expect(actionElement).to.exist;\n\n const rect = actionElement.getBoundingClientRect();\n const topY = rect.top + 5; // Near top of first action\n const bottomY = rect.bottom + 5; // Below first action\n\n // Drag over at top\n const dragOverEventTop = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: topY\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEventTop);\n await node2Element.updateComplete;\n\n // Check drop index is at beginning\n let externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo.dropIndex).to.equal(0);\n\n // Drag over at bottom\n const dragOverEventBottom = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: bottomY\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEventBottom);\n await node2Element.updateComplete;\n\n // Check drop index is at end\n externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo.dropIndex).to.equal(1);\n });\n\n it('should not accept drops from the same node', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Try to drop action from node-1 onto node-1\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1', // Same as target\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node1Element.dispatchEvent(dragOverEvent);\n await node1Element.updateComplete;\n\n // Verify external drag info was NOT stored (rejected due to same source)\n const externalDragInfo = (node1Element as any).externalDragInfo;\n expect(externalDragInfo).to.be.null;\n });\n\n it('should clear external drag state and hide placeholder', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate drag over\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Verify placeholder exists\n let placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.exist;\n\n // Clear the external drag state (simulating drag leaving the node)\n (node2Element as any).externalDragInfo = null;\n // Trigger re-render\n node2Element.requestUpdate();\n await node2Element.updateComplete;\n\n // Verify placeholder is gone\n placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.not.exist;\n });\n\n it('should have sortable list for internal drag support', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Verify sortable list exists for internal drag (reordering within same node)\n const sortableList = node1Element.querySelector('temba-sortable-list');\n expect(sortableList).to.exist;\n\n // Verify it has external drag enabled\n expect(sortableList.hasAttribute('externaldrag')).to.be.true;\n });\n\n it('should only accept drops on execute_actions nodes', async () => {\n const routerNodeUI: NodeUI = {\n position: { left: 100, top: 100 },\n type: 'split_by_expression', // Not execute_actions\n config: {}\n };\n\n const routerNode: Node = {\n uuid: 'router-node',\n actions: [],\n exits: [{ uuid: 'exit-1', destination_uuid: null }],\n router: {\n type: 'switch',\n operand: '@input',\n cases: [],\n categories: [],\n default_category_uuid: 'cat-1'\n }\n };\n\n const routerElement = await fixture<CanvasNode>(html`\n <temba-flow-node\n .node=${routerNode}\n .ui=${routerNodeUI}\n ></temba-flow-node>\n `);\n\n await routerElement.updateComplete;\n\n // Try to drag over a non-execute_actions node\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n routerElement.dispatchEvent(dragOverEvent);\n await routerElement.updateComplete;\n\n // Verify external drag info was NOT stored (rejected due to wrong node type)\n const externalDragInfo = (routerElement as any).externalDragInfo;\n expect(externalDragInfo).to.be.null;\n });\n});\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { expect, assert } from '@open-wc/testing';
|
|
2
|
+
import { CanvasMenu } from '../src/flow/CanvasMenu';
|
|
3
|
+
import { assertScreenshot, getClip, getComponent } from './utils.test';
|
|
4
|
+
describe('temba-canvas-menu', () => {
|
|
5
|
+
const createCanvasMenu = async () => {
|
|
6
|
+
const component = (await getComponent('temba-canvas-menu', {}, '', 250, 250));
|
|
7
|
+
await component.updateComplete;
|
|
8
|
+
return component;
|
|
9
|
+
};
|
|
10
|
+
it('can be created', async () => {
|
|
11
|
+
const menu = await createCanvasMenu();
|
|
12
|
+
assert.instanceOf(menu, CanvasMenu);
|
|
13
|
+
expect(menu.open).to.be.false;
|
|
14
|
+
});
|
|
15
|
+
it('is not visible when closed', async () => {
|
|
16
|
+
var _a;
|
|
17
|
+
const menu = await createCanvasMenu();
|
|
18
|
+
expect(menu.open).to.be.false;
|
|
19
|
+
// verify no menu is rendered
|
|
20
|
+
const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
|
|
21
|
+
expect(menuElement).to.be.null;
|
|
22
|
+
});
|
|
23
|
+
it('shows menu when opened', async () => {
|
|
24
|
+
var _a;
|
|
25
|
+
const menu = await createCanvasMenu();
|
|
26
|
+
// open the menu
|
|
27
|
+
menu.show(100, 100, { x: 50, y: 50 });
|
|
28
|
+
await menu.updateComplete;
|
|
29
|
+
expect(menu.open).to.be.true;
|
|
30
|
+
expect(menu.x).to.equal(100);
|
|
31
|
+
expect(menu.y).to.equal(100);
|
|
32
|
+
// verify menu is rendered
|
|
33
|
+
const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
|
|
34
|
+
expect(menuElement).to.not.be.null;
|
|
35
|
+
await assertScreenshot('canvas-menu/open', getClip(menu));
|
|
36
|
+
});
|
|
37
|
+
it('has three menu items', async () => {
|
|
38
|
+
var _a;
|
|
39
|
+
const menu = await createCanvasMenu();
|
|
40
|
+
menu.show(100, 100, { x: 50, y: 50 });
|
|
41
|
+
await menu.updateComplete;
|
|
42
|
+
const menuItems = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.menu-item');
|
|
43
|
+
expect(menuItems === null || menuItems === void 0 ? void 0 : menuItems.length).to.equal(3);
|
|
44
|
+
// check menu item titles
|
|
45
|
+
const titles = Array.from(menuItems || []).map((item) => { var _a; return (_a = item.querySelector('.menu-item-title')) === null || _a === void 0 ? void 0 : _a.textContent; });
|
|
46
|
+
expect(titles).to.deep.equal([
|
|
47
|
+
'Add Action',
|
|
48
|
+
'Add Split',
|
|
49
|
+
'Add Sticky Note'
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
it('closes when close() is called', async () => {
|
|
53
|
+
const menu = await createCanvasMenu();
|
|
54
|
+
menu.show(100, 100, { x: 50, y: 50 });
|
|
55
|
+
await menu.updateComplete;
|
|
56
|
+
expect(menu.open).to.be.true;
|
|
57
|
+
menu.close();
|
|
58
|
+
await menu.updateComplete;
|
|
59
|
+
expect(menu.open).to.be.false;
|
|
60
|
+
});
|
|
61
|
+
it('fires selection event when menu item is clicked', async () => {
|
|
62
|
+
var _a;
|
|
63
|
+
const menu = await createCanvasMenu();
|
|
64
|
+
menu.show(100, 100, { x: 50, y: 50 });
|
|
65
|
+
await menu.updateComplete;
|
|
66
|
+
let selectionFired = false;
|
|
67
|
+
let selectionDetail = null;
|
|
68
|
+
menu.addEventListener('temba-selection', (event) => {
|
|
69
|
+
selectionFired = true;
|
|
70
|
+
selectionDetail = event.detail;
|
|
71
|
+
});
|
|
72
|
+
// click on sticky note option (now the third item)
|
|
73
|
+
const menuItems = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.menu-item');
|
|
74
|
+
const stickyItem = menuItems === null || menuItems === void 0 ? void 0 : menuItems[2];
|
|
75
|
+
stickyItem.click();
|
|
76
|
+
await menu.updateComplete;
|
|
77
|
+
expect(selectionFired).to.be.true;
|
|
78
|
+
expect(selectionDetail).to.deep.equal({
|
|
79
|
+
action: 'sticky',
|
|
80
|
+
position: { x: 50, y: 50 }
|
|
81
|
+
});
|
|
82
|
+
expect(menu.open).to.be.false;
|
|
83
|
+
});
|
|
84
|
+
it('adjusts position to stay within viewport bounds', async () => {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
const menu = await createCanvasMenu();
|
|
87
|
+
// open menu at position that would go off screen
|
|
88
|
+
const viewportWidth = window.innerWidth;
|
|
89
|
+
const viewportHeight = window.innerHeight;
|
|
90
|
+
const margin = 10; // matches the margin in CanvasMenu
|
|
91
|
+
// position that would go off the right and bottom edges
|
|
92
|
+
menu.show(viewportWidth - 50, viewportHeight - 50, {
|
|
93
|
+
x: 100,
|
|
94
|
+
y: 100
|
|
95
|
+
});
|
|
96
|
+
await menu.updateComplete;
|
|
97
|
+
// wait for position adjustment
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
99
|
+
await menu.updateComplete;
|
|
100
|
+
const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
|
|
101
|
+
expect(menuElement).to.not.be.null;
|
|
102
|
+
const menuRect = menuElement.getBoundingClientRect();
|
|
103
|
+
// verify menu stays within viewport with margin
|
|
104
|
+
expect(menuRect.right).to.be.at.most(viewportWidth - margin);
|
|
105
|
+
expect(menuRect.bottom).to.be.at.most(viewportHeight - margin);
|
|
106
|
+
// verify click position is preserved (not adjusted)
|
|
107
|
+
let selectionFired = false;
|
|
108
|
+
let selectionDetail = null;
|
|
109
|
+
menu.addEventListener('temba-selection', (event) => {
|
|
110
|
+
selectionFired = true;
|
|
111
|
+
selectionDetail = event.detail;
|
|
112
|
+
});
|
|
113
|
+
const menuItems = (_b = menu.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.menu-item');
|
|
114
|
+
const actionItem = menuItems === null || menuItems === void 0 ? void 0 : menuItems[0];
|
|
115
|
+
actionItem.click();
|
|
116
|
+
await menu.updateComplete;
|
|
117
|
+
expect(selectionFired).to.be.true;
|
|
118
|
+
// click position should remain unchanged
|
|
119
|
+
expect(selectionDetail.position).to.deep.equal({ x: 100, y: 100 });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=temba-canvas-menu.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temba-canvas-menu.test.js","sourceRoot":"","sources":["../../test/temba-canvas-menu.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, assert } from '@open-wc/testing';\nimport { CanvasMenu } from '../src/flow/CanvasMenu';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\ndescribe('temba-canvas-menu', () => {\n const createCanvasMenu = async () => {\n const component = (await getComponent(\n 'temba-canvas-menu',\n {},\n '',\n 250,\n 250\n )) as CanvasMenu;\n await component.updateComplete;\n return component;\n };\n\n it('can be created', async () => {\n const menu = await createCanvasMenu();\n assert.instanceOf(menu, CanvasMenu);\n expect(menu.open).to.be.false;\n });\n\n it('is not visible when closed', async () => {\n const menu = await createCanvasMenu();\n expect(menu.open).to.be.false;\n\n // verify no menu is rendered\n const menuElement = menu.shadowRoot?.querySelector('.menu');\n expect(menuElement).to.be.null;\n });\n\n it('shows menu when opened', async () => {\n const menu = await createCanvasMenu();\n\n // open the menu\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n expect(menu.open).to.be.true;\n expect(menu.x).to.equal(100);\n expect(menu.y).to.equal(100);\n\n // verify menu is rendered\n const menuElement = menu.shadowRoot?.querySelector('.menu');\n expect(menuElement).to.not.be.null;\n\n await assertScreenshot('canvas-menu/open', getClip(menu));\n });\n\n it('has three menu items', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n expect(menuItems?.length).to.equal(3);\n\n // check menu item titles\n const titles = Array.from(menuItems || []).map(\n (item) => item.querySelector('.menu-item-title')?.textContent\n );\n expect(titles).to.deep.equal([\n 'Add Action',\n 'Add Split',\n 'Add Sticky Note'\n ]);\n });\n\n it('closes when close() is called', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n expect(menu.open).to.be.true;\n\n menu.close();\n await menu.updateComplete;\n\n expect(menu.open).to.be.false;\n });\n\n it('fires selection event when menu item is clicked', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n let selectionFired = false;\n let selectionDetail = null;\n\n menu.addEventListener('temba-selection', (event: any) => {\n selectionFired = true;\n selectionDetail = event.detail;\n });\n\n // click on sticky note option (now the third item)\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n const stickyItem = menuItems?.[2] as HTMLElement;\n stickyItem.click();\n await menu.updateComplete;\n\n expect(selectionFired).to.be.true;\n expect(selectionDetail).to.deep.equal({\n action: 'sticky',\n position: { x: 50, y: 50 }\n });\n expect(menu.open).to.be.false;\n });\n\n it('adjusts position to stay within viewport bounds', async () => {\n const menu = await createCanvasMenu();\n\n // open menu at position that would go off screen\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const margin = 10; // matches the margin in CanvasMenu\n\n // position that would go off the right and bottom edges\n menu.show(viewportWidth - 50, viewportHeight - 50, {\n x: 100,\n y: 100\n });\n await menu.updateComplete;\n\n // wait for position adjustment\n await new Promise((resolve) => setTimeout(resolve, 100));\n await menu.updateComplete;\n\n const menuElement = menu.shadowRoot?.querySelector('.menu') as HTMLElement;\n expect(menuElement).to.not.be.null;\n\n const menuRect = menuElement.getBoundingClientRect();\n\n // verify menu stays within viewport with margin\n expect(menuRect.right).to.be.at.most(viewportWidth - margin);\n expect(menuRect.bottom).to.be.at.most(viewportHeight - margin);\n\n // verify click position is preserved (not adjusted)\n let selectionFired = false;\n let selectionDetail = null;\n\n menu.addEventListener('temba-selection', (event: any) => {\n selectionFired = true;\n selectionDetail = event.detail;\n });\n\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n const actionItem = menuItems?.[0] as HTMLElement;\n actionItem.click();\n await menu.updateComplete;\n\n expect(selectionFired).to.be.true;\n // click position should remain unchanged\n expect(selectionDetail.position).to.deep.equal({ x: 100, y: 100 });\n });\n});\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, assert } from '@open-wc/testing';
|
|
2
|
+
import { FloatingTab } from '../src/display/FloatingTab';
|
|
3
|
+
import { assertScreenshot, getClip, getComponent } from './utils.test';
|
|
4
|
+
describe('temba-floating-tab', () => {
|
|
5
|
+
it('can be created', async () => {
|
|
6
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
7
|
+
icon: 'phone',
|
|
8
|
+
label: 'Phone Simulator',
|
|
9
|
+
color: '#10b981',
|
|
10
|
+
top: 100
|
|
11
|
+
}));
|
|
12
|
+
assert.instanceOf(tab, FloatingTab);
|
|
13
|
+
expect(tab.icon).to.equal('phone');
|
|
14
|
+
expect(tab.label).to.equal('Phone Simulator');
|
|
15
|
+
expect(tab.color).to.equal('#10b981');
|
|
16
|
+
expect(tab.top).to.equal(100);
|
|
17
|
+
expect(tab.hidden).to.equal(false);
|
|
18
|
+
await assertScreenshot('floating-tab/default', getClip(tab));
|
|
19
|
+
});
|
|
20
|
+
it('can be hidden', async () => {
|
|
21
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
22
|
+
icon: 'phone',
|
|
23
|
+
label: 'Phone Simulator',
|
|
24
|
+
color: '#10b981',
|
|
25
|
+
hidden: true
|
|
26
|
+
}));
|
|
27
|
+
expect(tab.hidden).to.equal(true);
|
|
28
|
+
expect(tab.classList.contains('hidden')).to.equal(true);
|
|
29
|
+
await assertScreenshot('floating-tab/hidden', getClip(tab));
|
|
30
|
+
});
|
|
31
|
+
it('shows label on hover', async () => {
|
|
32
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
33
|
+
icon: 'phone',
|
|
34
|
+
label: 'Phone Simulator',
|
|
35
|
+
color: '#6366f1'
|
|
36
|
+
}));
|
|
37
|
+
const tabElement = tab.shadowRoot.querySelector('.tab');
|
|
38
|
+
expect(tabElement).to.exist;
|
|
39
|
+
// simulate hover state
|
|
40
|
+
const labelElement = tab.shadowRoot.querySelector('.label');
|
|
41
|
+
expect(labelElement).to.exist;
|
|
42
|
+
await assertScreenshot('floating-tab/hover', getClip(tab));
|
|
43
|
+
});
|
|
44
|
+
it('fires click event', async () => {
|
|
45
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
46
|
+
icon: 'clock',
|
|
47
|
+
label: 'History',
|
|
48
|
+
color: '#8b5cf6'
|
|
49
|
+
}));
|
|
50
|
+
let clicked = false;
|
|
51
|
+
tab.addEventListener('temba-button-clicked', () => {
|
|
52
|
+
clicked = true;
|
|
53
|
+
});
|
|
54
|
+
const tabElement = tab.shadowRoot.querySelector('.tab');
|
|
55
|
+
tabElement.click();
|
|
56
|
+
expect(clicked).to.equal(true);
|
|
57
|
+
});
|
|
58
|
+
it('supports different colors', async () => {
|
|
59
|
+
const tab1 = (await getComponent('temba-floating-tab', {
|
|
60
|
+
icon: 'phone',
|
|
61
|
+
label: 'Phone',
|
|
62
|
+
color: '#10b981',
|
|
63
|
+
top: 100
|
|
64
|
+
}));
|
|
65
|
+
const tab2 = (await getComponent('temba-floating-tab', {
|
|
66
|
+
icon: 'globe',
|
|
67
|
+
label: 'Translation',
|
|
68
|
+
color: '#6b7280',
|
|
69
|
+
top: 200
|
|
70
|
+
}));
|
|
71
|
+
const tab3 = (await getComponent('temba-floating-tab', {
|
|
72
|
+
icon: 'clock',
|
|
73
|
+
label: 'History',
|
|
74
|
+
color: '#8b5cf6',
|
|
75
|
+
top: 300
|
|
76
|
+
}));
|
|
77
|
+
await assertScreenshot('floating-tab/green', getClip(tab1));
|
|
78
|
+
await assertScreenshot('floating-tab/gray', getClip(tab2));
|
|
79
|
+
await assertScreenshot('floating-tab/purple', getClip(tab3));
|
|
80
|
+
});
|
|
81
|
+
it('supports custom positioning', async () => {
|
|
82
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
83
|
+
icon: 'phone',
|
|
84
|
+
label: 'Phone Simulator',
|
|
85
|
+
color: '#10b981',
|
|
86
|
+
top: 250
|
|
87
|
+
}));
|
|
88
|
+
expect(tab.top).to.equal(250);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=temba-floating-tab.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temba-floating-tab.test.js","sourceRoot":"","sources":["../../test/temba-floating-tab.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, assert } from '@open-wc/testing';\nimport { FloatingTab } from '../src/display/FloatingTab';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\ndescribe('temba-floating-tab', () => {\n it('can be created', async () => {\n const tab = (await getComponent('temba-floating-tab', {\n icon: 'phone',\n label: 'Phone Simulator',\n color: '#10b981',\n top: 100\n })) as FloatingTab;\n\n assert.instanceOf(tab, FloatingTab);\n expect(tab.icon).to.equal('phone');\n expect(tab.label).to.equal('Phone Simulator');\n expect(tab.color).to.equal('#10b981');\n expect(tab.top).to.equal(100);\n expect(tab.hidden).to.equal(false);\n\n await assertScreenshot('floating-tab/default', getClip(tab));\n });\n\n it('can be hidden', async () => {\n const tab = (await getComponent('temba-floating-tab', {\n icon: 'phone',\n label: 'Phone Simulator',\n color: '#10b981',\n hidden: true\n })) as FloatingTab;\n\n expect(tab.hidden).to.equal(true);\n expect(tab.classList.contains('hidden')).to.equal(true);\n\n await assertScreenshot('floating-tab/hidden', getClip(tab));\n });\n\n it('shows label on hover', async () => {\n const tab = (await getComponent('temba-floating-tab', {\n icon: 'phone',\n label: 'Phone Simulator',\n color: '#6366f1'\n })) as FloatingTab;\n\n const tabElement = tab.shadowRoot.querySelector('.tab') as HTMLElement;\n expect(tabElement).to.exist;\n\n // simulate hover state\n const labelElement = tab.shadowRoot.querySelector('.label') as HTMLElement;\n expect(labelElement).to.exist;\n\n await assertScreenshot('floating-tab/hover', getClip(tab));\n });\n\n it('fires click event', async () => {\n const tab = (await getComponent('temba-floating-tab', {\n icon: 'clock',\n label: 'History',\n color: '#8b5cf6'\n })) as FloatingTab;\n\n let clicked = false;\n tab.addEventListener('temba-button-clicked', () => {\n clicked = true;\n });\n\n const tabElement = tab.shadowRoot.querySelector('.tab') as HTMLElement;\n tabElement.click();\n\n expect(clicked).to.equal(true);\n });\n\n it('supports different colors', async () => {\n const tab1 = (await getComponent('temba-floating-tab', {\n icon: 'phone',\n label: 'Phone',\n color: '#10b981',\n top: 100\n })) as FloatingTab;\n\n const tab2 = (await getComponent('temba-floating-tab', {\n icon: 'globe',\n label: 'Translation',\n color: '#6b7280',\n top: 200\n })) as FloatingTab;\n\n const tab3 = (await getComponent('temba-floating-tab', {\n icon: 'clock',\n label: 'History',\n color: '#8b5cf6',\n top: 300\n })) as FloatingTab;\n\n await assertScreenshot('floating-tab/green', getClip(tab1));\n await assertScreenshot('floating-tab/gray', getClip(tab2));\n await assertScreenshot('floating-tab/purple', getClip(tab3));\n });\n\n it('supports custom positioning', async () => {\n const tab = (await getComponent('temba-floating-tab', {\n icon: 'phone',\n label: 'Phone Simulator',\n color: '#10b981',\n top: 250\n })) as FloatingTab;\n\n expect(tab.top).to.equal(250);\n });\n});\n"]}
|